Каковы некоторые виды использования decltype (auto)?

151

В c ++ 14 decltype(auto)введена идиома.

Обычно его используют для того, чтобы autoобъявления могли использовать decltypeправила для данного выражения .

В поисках примеров «хорошего» использования идиомы я могу думать только о таких вещах, как ( Скотт Мейерс ), а именно о выводе типа возвращаемого значения функции :

template<typename ContainerType, typename IndexType>                // C++14
decltype(auto) grab(ContainerType&& container, IndexType&& index)
{
  authenticateUser();
  return std::forward<ContainerType>(container)[std::forward<IndexType>(index)];
}

Есть ли другие примеры, где эта новая языковая функция полезна?

Никос Атанасиу
источник
2
В этом посте в основном предлагается попытаться избежать этой идиомы, потому что при ее использовании вы даете меньше возможностей для оптимизации вашему компилятору stackoverflow.com/a/20092875/2485710
user2485710
Когда-то я использовал decltype(auto)что-то похожее template<class U, V> decltype(auto) first(std::pair<U, V>& p) { return p.first; }, хотя потом понял, что должен был использовать, return (p.first);что удивительно работает (но IIRC это даже задумано).
DYP
@ user2485710 не уверен, что речь идет конкретно об оптимизации, больше вероятность несчастных случаев, если это decltype(auto)может привести к тому, что что-то будет скопировано / перемещено в объявленный объект, вопреки ожиданиям.
underscore_d

Ответы:

170

Переадресация типа возврата в универсальном коде

Для неуниверсального кода, как и в приведенном вами примере, вы можете вручную выбрать ссылку в качестве типа возврата:

auto const& Example(int const& i) 
{ 
    return i; 
}

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

template<class Fun, class... Args>
decltype(auto) Example(Fun fun, Args&&... args) 
{ 
    return fun(std::forward<Args>(args)...); 
}

Задержка вывода типа возврата в рекурсивных шаблонах

В этих вопросах и ответах несколько дней назад возникла бесконечная рекурсия во время создания шаблона, когда тип возврата шаблона был указан decltype(iter(Int<i-1>{}))вместо decltype(auto).

template<int i> 
struct Int {};

constexpr auto iter(Int<0>) -> Int<0>;

template<int i>
constexpr auto iter(Int<i>) -> decltype(auto) 
{ return iter(Int<i-1>{}); }

int main() { decltype(iter(Int<10>{})) a; }

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

Другое использование

Вы также можете использовать decltype(auto)в других контекстах, например, в проекте стандарта N3936 также говорится

7.1.6.4 автоматический спецификатор [dcl.spec.auto]

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

2 Тип заполнителя может появляться с объявителем функции в decl-specier-seq, type-specier-seq, translation-function-id или trailing-return-type в любом контексте, где такой декларатор допустим . Если декларатор функции включает в себя тип конечного возврата (8.3.5), это указывает на объявленный тип возврата функции. Если объявленный тип возврата функции содержит тип заполнителя, тип возврата функции выводится из операторов возврата в теле функции, если таковые имеются.

Черновик также содержит этот пример инициализации переменной:

int i;
int&& f();
auto x3a = i;                  // decltype(x3a) is int
decltype(auto) x3d = i;        // decltype(x3d) is int
auto x4a = (i);                // decltype(x4a) is int
decltype(auto) x4d = (i);      // decltype(x4d) is int&
auto x5a = f();                // decltype(x5a) is int
decltype(auto) x5d = f();      // decltype(x5d) is int&&
auto x6a = { 1, 2 };           // decltype(x6a) is std::initializer_list<int>
decltype(auto) x6d = { 1, 2 }; // error, { 1, 2 } is not an expression
auto *x7a = &i;                // decltype(x7a) is int*
decltype(auto)*x7d = &i;       // error, declared type is not plain decltype(auto)
TemplateRex
источник
17
Различное поведение (i)против iновой вещи в C ++ 14?
Данвил
14
@Danvil decltype(expr)и decltype((expr))уже отличаются в C ++ 11, это обобщает это поведение.
TemplateRex
13
Я только что узнал это, похоже на ужасное дизайнерское решение ... добавление пунктуального нюанса к синтаксическому значению скобок.
Калер
Примером, всегда вызывающим это отвращение, является однострочный синтаксис файла в строку (также упоминается в этой ссылке). Каждая часть этого кажется отсталой. Вы можете вообще не ожидать двусмысленности и принудительно удалить лишние скобки из образца; вы ожидаете, что двусмысленность разрешится в процессе исключения согласно SFINAE, но потенциальные кандидаты, отличные от декларации, исключаются заранее (SF - AE); и в отчаянии вы можете двигаться дальше, как только он скомпилирует мышление, что произвольные параны решают двусмысленность, но они вводят это. Самое неприятное для профессоров CS101, я думаю.
Джон П
@TemplateRex: О задержке разрешения возвращаемого типа в указанном вопросе. Насколько я вижу, в конкретном сценарии простая autoзадача также справилась бы с задачей, так как результат все равно возвращается по значению ... Или я пропустил что-то?
Аконкагуа
36

Цитирую материал отсюда :

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

  • Например, учитывая функции ниже:


   string  lookup1();
   string& lookup2();

  • В C ++ 11 мы могли бы написать следующие функции-оболочки, которые не забывают сохранить ссылку на тип возвращаемого значения:

   string  look_up_a_string_1() { return lookup1(); }
   string& look_up_a_string_2() { return lookup2(); }

  • В C ++ 14 мы можем автоматизировать это:

   decltype(auto) look_up_a_string_1() { return lookup1(); }
   decltype(auto) look_up_a_string_2() { return lookup2(); }

  • Тем decltype(auto)не менее, он не предназначен для того, чтобы широко использоваться за его пределами.

  • В частности, хотя его можно использовать для объявления локальных переменных , это, вероятно, просто антипаттерн, поскольку ссылка на локальную переменную не должна зависеть от выражения инициализации.

  • Кроме того, он чувствителен к тому, как вы пишете инструкцию возврата.

  • Например, две функции ниже имеют разные типы возврата:


   decltype(auto) look_up_a_string_1() { auto str = lookup1(); return str; }
   decltype(auto) look_up_a_string_2() { auto str = lookup2(); return(str); }

  • Первое возвращает string, второе возвращает string&, которое является ссылкой на локальную переменную str.

Из предложения вы можете увидеть больше предполагаемого использования.

101010
источник
3
Почему бы просто не использовать autoдля возврата?
BЈовић
@ BЈовић также может работать с обобщенным выводом типа возврата (т. Е. С autoвозвратом), но ОП специально спрашивал об использовании decltype(auto).
101010
3
Вопрос все еще актуален, хотя. Каким будет тип возвращаемого значения auto lookup_a_string() { ... } ? Это всегда не ссылочный тип? И поэтому auto lookup_a_string() ->decltype(auto) { ... }необходимо принудительно разрешить возврат (в некоторых случаях) ссылок?
Аарон МакДейд
@AaronMcDaid Deductible autoопределяется в терминах шаблона передачи по значению, поэтому да, он не может быть ссылкой. Пожалуйста, подождите, autoможет быть что угодно, включая ссылку, конечно.
любопытный парень
4
Еще один пример, который стоит упомянуть, это возвращение элемента a std::vector. Скажи, что у тебя есть template<typename T> struct S { auto & operator[](std::size_t i) { return v[i]; } std::vector<T> v; }. Затем S<bool>::operator[]вернет свисающие ссылки из-за специализации std::vector<bool>. Изменение типа возвращаемого значения позволяет decltype(auto)обойти эту проблему.
Xoph