如果你有一个函数返回一个不能移动的临时值。
Foo some_function();
auto&& f = some_function();
这是合法的。 auto f = some_function(); 要么复制(这可能很昂贵),要么编译失败(如果类也无法复制)。
一般来说,auto&& 会根据初始化的内容推导出 r 或左值引用,如果使用临时值初始化,则会延长其生命周期,同时让您可以作为左值访问它。
“just-loop”模式是一个经典的用法:
for( auto&& x : some_range )
在生成的代码中实际上有一个auto&& x = *it;。
您不能将非常量左值引用绑定到临时对象,因此您的另一个选择是Widget const&,它不允许您在其生命周期内修改临时对象。
这种技术对于分解复杂的表达式并查看发生了什么也很有用。只要您不使用极其脆弱的表达式模板,您就可以将表达式a+b+c*d 转换为
auto&& c_times_d = d*d;
auto&& b_plus_c_times_d = b + decltype(c_times_d)c_times_d;
auto&& c_plus_b_plus_c_times_d = c + decltype(b_plus_c_times_d)b_plus_c_times_d;
现在您可以访问生命周期已延长的临时对象,并且您可以轻松地单步执行代码,或在复杂表达式的步骤之间引入额外的步骤:这是机械地发生的。
仅当您未能绑定 每个 子表达式时,对脆弱表达式模板的担忧才成立。 (请注意,使用-> 会生成无数您可能不会注意到的子表达式。)
当我想说“我正在按原样存储某个函数的返回值,而不进行复制”时,我使用auto&&,而表达式的类型并不重要。 auto 是我想制作本地副本的时候。
在通用代码中它非常有用。
Foo const& a(Foo*);
Bar a(Bar*);
template<class T>
auto do_stuff( T*ptr ) {
auto&& x = a(ptr);
}
在这里,如果您传递 Bar* 它会存储临时值,但如果您将 Foo* 传递给 do_stuff 它会存储 const&。
它做的最少。
这是一个返回不可移动不可复制对象的函数示例,以及auto&& 如何让您存储它。否则它是无用的,但它显示了它是如何工作的:
struct Foo {
Foo(&&)=delete;
Foo(int x) { std::cout << "Foo " << x << " constructed\n";
};
Foo test() {
return {3};
}
int main() {
auto&& f = test();
}