Существуют ли какие-либо реалистичные варианты использования переменных `decltype (auto)`?

Как из моего личного опыта, так и из ответов на вопросы, подобные Каковы некоторые виды использования decltype (auto )? Я могу найти множество ценных примеров использования decltype(auto) в качестве заполнителя типа возвращаемого значения функции.

Однако я серьезно изо всех сил пытаюсь придумать какой-либо допустимый (т.е. полезный, реалистичный, ценный) вариант использования decltype(auto) переменных. Единственная возможность, которая приходит на ум, - это сохранить результат функции, возвращающей decltype(auto), для последующего распространения, но auto&& можно было бы использовать и там, и это было бы проще.

Я даже просмотрел все свои проекты и эксперименты, и 391 вхождение decltype(auto) - все это заполнители возвращаемого типа.

Итак, существуют ли реалистичные варианты использования decltype(auto) переменных? Или эта функция полезна только при использовании в качестве заполнителя типа возвращаемого значения?


Как вы определяете «реалистичный»?

Я ищу вариант использования, который предоставляет значение (т.е. это не просто пример, показывающий, как работает функция), где decltype(auto) - идеальный выбор по сравнению с альтернативами, такими как auto&& или без объявления переменной вообще.

Проблемная область не имеет значения, это может быть какой-то неясный угловой случай метапрограммирования или загадочная конструкция функционального программирования. Однако этот пример должен был бы заставить меня сказать «Эй, это умно / красиво!», а использование любой другой функции для достижения того же эффекта потребует большего количества шаблонов или будет иметь какой-то недостаток.


person Vittorio Romeo    schedule 09.08.2019    source источник
comment
Как вы определяете реалистичность?   -  person Nicol Bolas    schedule 10.08.2019
comment
@NicolBolas: я ищу какой-нибудь вариант использования, который предоставляет значение (т.е. это не просто пример, показывающий, как работает функция), где decltype(auto) - идеальный выбор по сравнению с альтернативами, такими как auto&& или вообще не объявлять переменную. Домен не имеет значения, это может быть какой-то неясный угловой случай метапрограммирования. Но этот пример должен был бы заставить меня пойти Эй, это умно!, а использование любой другой функции для достижения того же эффекта потребует больше шаблонов или будет иметь какой-то недостаток. Извините, что не могу сказать точнее.   -  person Vittorio Romeo    schedule 10.08.2019
comment
@Eljay: Я польщен! Я могу привести множество примеров для decltype(auto)-возвращающих функций ... но переменные сейчас меня сбивают с толку :)   -  person Vittorio Romeo    schedule 10.08.2019


Ответы (2)


Вероятно, не очень глубокий ответ, но в основном decltype(auto) был предлагается использовать для вывода типа возвращаемого значения, чтобы иметь возможность выводить ссылки, когда тип возвращаемого значения фактически является ссылкой (в отличие от простого auto, который никогда не выводит ссылку, или auto&&, который всегда будет это делать).

Тот факт, что его также можно использовать для объявления переменных, не обязательно означает, что должны быть сценарии лучше, чем другие. Действительно, использование decltype(auto) в объявлении переменной просто усложнит чтение кода, учитывая, что для объявления переменной он имеет точно такое же значение. С другой стороны, форма auto&& позволяет объявлять постоянную переменную, а decltype(auto) - нет.

person cbuchart    schedule 14.08.2019
comment
Я не понимаю, как auto && позволяет объявлять постоянную переменную (ссылки никогда не константны на верхнем уровне), тогда как decltype (auto) позволяет делать это без каких-либо проблем. - person L. F.; 16.08.2019
comment
@ L.F. извините за поздний ответ, вероятно, я выразился не полностью: я имел в виду, что есть определенные случаи, когда вы можете преобразовать в константу, добавив модификатор const в auto&&, тогда как decltype(auto) не позволяет вам это сделать. Я знаю, что это невозможно использовать во всех сценариях (как в int a; int& b=a; const auto&& c=b;), но это возможно в таких случаях, как int foo() { return 0; } const auto&& a=foo();. Наверное, не очень полезно, но возможно. В любом случае, поправьте меня, если я что-то упускаю. - person cbuchart; 21.08.2019

По сути, случай переменных такой же, как и для функций. Идея в том, что мы сохраняем результат вызова функции с переменной decltype(auto):

decltype(auto) result = /* function invocation */;

Тогда result

  • не ссылочный тип, если результатом является prvalue,

  • ссылочный тип lvalue (возможно, квалифицированный cv), если результатом является lvalue, или

  • ссылочный тип rvalue, если результатом является xvalue.

Теперь нам нужна новая версия forward, чтобы различать регистр prvalue и случай xvalue: (имя forward избегается во избежание проблем с ADL)

template <typename T>
T my_forward(std::remove_reference_t<T>& arg)
{
    return std::forward<T>(arg);
}

А затем используйте

my_forward<decltype(result)>(result)

В отличие от std::forward, эта функция используется для пересылки decltype(auto) переменных. Следовательно, он не обязательно возвращает ссылочный тип, и предполагается, что он будет вызываться с decltype(variable), который может быть T, T& или T&&, чтобы он мог различать lvalues, xvalues ​​и prvalues. Таким образом, если result

  • не ссылочный тип, то вторая перегрузка вызывается с не ссылочным T, и возвращается не ссылочный тип, что приводит к prvalue;

  • ссылочный тип lvalue, тогда первая перегрузка вызывается с T&, и возвращается T&, что приводит к lvalue;

  • ссылочный тип rvalue, затем вызывается вторая перегрузка с T&&, и возвращается T&&, в результате чего получается xvalue.

Вот пример. Учтите, что вы хотите обернуть std::invoke и что-то напечатать в журнале: (пример приведен только для иллюстрации)

template <typename F, typename... Args>
decltype(auto) my_invoke(F&& f, Args&&... args)
{
    decltype(auto) result = std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
    my_log("invoke", result); // for illustration only
    return my_forward<decltype(result)>(result);
}

Теперь, если выражение вызова

  • prvalue, тогда result - это не ссылочный тип, а функция возвращает не ссылочный тип;

  • неконстантное lvalue, тогда result - неконстантная ссылка lvalue, и функция возвращает неконстантный ссылочный тип lvalue;

  • const lvalue, тогда result - это ссылка на const lvalue, а функция возвращает ссылочный тип const lvalue;

  • xvalue, тогда result - это ссылочный тип rvalue, а функция возвращает ссылочный тип rvalue.

Учитывая следующие функции:

int f();
int& g();
const int& h();
int&& i();

справедливы следующие утверждения:

static_assert(std::is_same_v<decltype(my_invoke(f)), int>);
static_assert(std::is_same_v<decltype(my_invoke(g)), int&>);
static_assert(std::is_same_v<decltype(my_invoke(h)), const int&>);
static_assert(std::is_same_v<decltype(my_invoke(i)), int&&>);

(живая демонстрация, переместить только тестовый пример)

Если вместо этого используется auto&&, у кода возникнут проблемы с различием между prvalues ​​и xvalues.

person L. F.    schedule 10.08.2019
comment
Я думаю, что это шаг в правильном направлении, но ваш пример вызывает ненужную копию, когда f возвращает prvalue, и препятствует обязательному исключению копий. Взгляните на мою версию здесь: gcc.godbolt.org/z/awuVxy - person Vittorio Romeo; 10.08.2019
comment
Разместил здесь следующий вопрос: stackoverflow.com/questions/57444893/. Я не уверен, что decltype(auto) - лучшее решение этой проблемы. Решение on_scope_success кажется чище. - person Vittorio Romeo; 10.08.2019
comment
@VittorioRomeo: Я не уверен, что decltype (auto) - лучшее решение этой проблемы. И именно поэтому ваш вопрос основан на мнении: кто решает, что лучше? Код LF очень удобочитаем, а ваш альтернативный пример ... странный. Непонятно, почему вы делаете то, что делаете, или какова цель этого. Это много дополнительного кода только для того, чтобы возможно избежать копирования / перемещения (поскольку почти каждый компилятор использует этот код в NVRO). - person Nicol Bolas; 12.08.2019
comment
@VittorioRomeo: Действительно, ваше собственное определение реалистичности конкретно указывает, что использование любой другой функции для достижения того же эффекта потребует большего количества шаблонов. Что ж, ваша альтернатива определенно требует большего количества шаблонов. Итак, по вашему собственному определению, это реалистичный пример. - person Nicol Bolas; 12.08.2019
comment
@NicolBolas: речь не идет о NRVO, версия, представленная в этом ответе, вводит дополнительную копию! См. gcc.godbolt.org/z/awuVxy. Я не считаю это незначительным недостатком, я считаю это серьезным недостатком. Например, он предотвращает использование объектов, предназначенных только для перемещения. Так что нет, он не дает такого же эффекта, как моя версия. Если дополнительная копия не имеет значения, мы могли бы также использовать auto или auto&& вместо decltype(auto). - person Vittorio Romeo; 12.08.2019
comment
@VittorioRomeo Вы правы, мой предыдущий ответ совершенно неверен. Я обновил свой ответ лучшей версией (не прибегая к if constexpr). Я также добавил тестовый пример типа "только движение". - person L. F.; 14.08.2019
comment
@ L.F .: это все еще кажется неправильным. Любое prvalue копируется, как и раньше. И РВО не возможно. См. gcc.godbolt.org/z/gNCk_x. - person Vittorio Romeo; 14.08.2019
comment
@Vittorio Я такой тупой. Теперь все должно быть правильно. - person L. F.; 14.08.2019
comment
@ L.F .: теперь он перемещается, а не копирует, но, насколько я понимаю, не позволяет RVO. - person Vittorio Romeo; 14.08.2019