#опытным
Второе место, в котором целесообразно использовать decltype(auto) и auto&& - это автоматический вывод типа возвращаемого значения функции. Вот тут между ними есть важные различия, которые мы сейчас и обсудим.
decltype(auto) bar() {
int i;
int& ref = i;
return ref;
}
decltype(auto) bar1() {
int i = 42;
return i;
}
decltype(auto) bar2() {
return 42;
}
decltype(auto) bar3() {
int i;
return std::move(i);
}
auto&& baz() {
int i;
int& ref = i;
return ref;
}
auto&& baz1() {
int i = 42;
return i;
}
auto&& baz2() {
return 42;
}
auto&& baz3() {
int i;
return std::move(i);
}Вот 2 семейства функций, которые возвращают объекты разной ссылочности и выводит тип возвращаемого значения по-разному. Давайте с помощью cppinsights посмотрим, какие реально типы возвращаемых значений выведутся:
int & bar() {
int i;
int & ref = i;
return ref;
}
int bar1() {
int i = 42;
return i;
}
int bar2() {
return 42;
}
typename std::remove_reference<int &>::type && bar3() {
int i;
return std::move(i);
}
int & baz() {
int i;
int & ref = i;
return ref;
}
int & baz1() {
int i = 42;
return i;
}
int && baz2() {
return 42;
}
typename std::remove_reference<int &>::type && baz3() {
int i;
return std::move(i);
}Единственная разница появляется, когда мы возвращаем из функции lvalue или prvalue объект. В этом случае auto&& выводит тип правой ссылки, а decltype(auto) - обычный бессылочный lvalue объект. И в этом факте вся проблема. Может быть ее не видно сейчас, когда мы возвращаем целочисленный литерал. Но задние ряды уже догадались. Давайте же перейдем к реальной проблеме:
struct CLASS {
CLASS(int i) : num{i} {
std::cout << "ctor " << num << std::endl;
}
~CLASS() {
std::cout << "dtor " << num << std::endl;
}
int num;
};
decltype(auto) get_class_decltype() {
return CLASS(0);
}
auto&& get_class_auto() {
return CLASS(42);
}
int main() {
decltype(auto) a = get_class_decltype();
std::cout << "separator" << std::endl;
decltype(auto) b = get_class_auto();
std::cout << "separator" << std::endl;
}Делаем простой класс, чтобы отследить его время жизни. И два варианта функции, которые возвращают rvalue объекты. Вот вывод для этого кода:
Вывод:
ctor 0
separator
ctor 42
dtor 42
separator
dtor 0
Здесь видно, что деструктор первого объекта вызывается в самом конце. При присваивании результата функции get_class_decltype() локальному объекту происходит оптимизация copy-elision, благодаря которой объект созданный внутри функции "бесшовно" становится локальным объектом caller'а без всяких мув- и копи- конструкторов.
А вот деструктор второго объекта, который мы планировали возвращать из get_class_auto, вызывается сразу после конструирования. На самом деле сигнатура функции get_class_auto выглядит так:
CLASS&& get_class_auto();
Мы пытаемся вернуть локальный объект функции через правую ссылку. Такое в С++ хоть и скомпилируется, но приведет к висячей ссылке и в последствии к UB при использовании ссылки.
Аналогичное будет происходить, если возвращать lvalue.
Какой вывод?
Если вам нужен вывод типов в возвращаемом значении функции - всегда используйте в типе возвращаемого значения функции decltype(auto) вместо auto&&, особенно в шаблонном коде. На это кстати есть еще одна причина, о которой речь пойдет в следующем посте.
Be safe. Stay cool.
#cppcore