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

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

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

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

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


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

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

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

Ответ 1

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

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

Тогда result - это

  • тип без ссылки, если результат является предварительным значением,

  • (возможно, cv-квалифицированный) ссылочный тип lvalue, если результатом является 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&&, так что он может различать lvalue, 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&&, в коде возникнут проблемы с разграничением значений prvalue и xvalue.

Ответ 2

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

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