В чём разница между decltype(auto) и auto&&?
Понятно, что типы вычисляются по разным механизмам, но есть ли разница в конечном результате?
Раз типы вычисляются по разным механизмам, то и разница в конечном результате присутствует, иначе зачем тогда было бы два способа (хотя в С++ куча мест где без причин есть несколько способов сделать одно и то же)?
#include <type_traits>
class foo {};
foo bar() { return foo{}; }
decltype(auto) var1{bar()};
auto && var2{bar()};
static_assert(::std::is_same_v<decltype(var1), class foo>);
static_assert(::std::is_same_v<decltype(var2), class foo &&>);
Безусловно разница есть, но в некоторых случаях могут быть и одинаковые результаты. Ничего удивительного в том, что разные подходы могут при некотором стечении обстоятельств давать одинаковые результаты быть не должно. Пример:
#include <type_traits>
int& f()
{
static int i = 42;
return i;
}
int g() { return 42; }
int main()
{
auto&& v1 = f();
decltype(auto) v2 = f();
auto&& v3 = g();
decltype(auto) v4 = g();
static_assert(std::is_same<decltype(v1), int&>::value);
static_assert(std::is_same<decltype(v2), int&>::value);
static_assert(std::is_same<decltype(v3), int&&>::value);
static_assert(std::is_same<decltype(v4), int>::value);
}
Для переменной, используемой в качестве результата f() и auto&& и decltype(auto) дадут одинаковые типы - int&. С функцией g() уже будут другие результаты - int&& и int соответственно.
decltype(auto) даёт тип объявления сущности, т.е. в случае функций объявленный тип результата: int& для f(), и int для g(). Подробнее можно посмотреть тут.
В свою очередь вывод для auto&& будет работать по схеме вывода типа параметра в шаблоне следующего вида:
template <class T>
void t(T&& a); // T вместо auto
Если вызвать t(f()) то тип a будет выведен как int&, а для t(g()) получится int&&.
Выражение вида auto&& foo = make_foo(params); будет корректно в большинстве случаев, за исключением некоторых прокси-объектов. Если make_foo возвращает тип bar, foo будет иметь тип bar&&, при этом время жизни возвращаемого значения будет расширено до времени жизни foo. Т.е. это корректно, как и в таком случае:
std::string&& str = std::string("foo");
std::string str2 = std::move(str);
Если make_foo возвращает bar&, const bar& или const bar&, foo будет иметь соответствующий тип. Это полезно, например, при работе с кортежами:
std::tuple<bar&, const bar&, bar&&> t = ...;
auto&& r1 = std::get<0>(t); // bar&
auto&& r2 = std::get<1>(t); // const bar&
auto&& r3 = std::get<2>(t); // bar&&
В случае decltype(auto) это перестаёт работать. В выражении decltype(auto) foo = make_foo(params);, если make_foo возвращает bar, foo будет иметь тип bar, т.е. значение будет сразу перемещено или будет применена оптимизация возвращаемого значения, что не всегда желательно.
Пример:
auto&& foo= makeBar(); // Bar&&
// перемещение или инициализация на месте, Эквивалентно vector.emplace_back(makeBar())
vector.emplace_back(foo);
Или
decltype(auto) baz = makeBar(); // Перемещение или инициализация на месте
vector.emplace_back(baz); // Копирование
Продвижение своими сайтами как стратегия роста и независимости