C ++ 0x右值引用和临时值

时间:2010-05-01 04:20:31

标签: c++ c++11 temporary rvalue-reference

(我在comp.std.c ++上询问了这个问题的变体,但没有得到答案。)

为什么在此代码中对f(arg)的调用会调用f的const ref重载?

void f(const std::string &); //less efficient
void f(std::string &&); //more efficient

void g(const char * arg)
{
     f(arg);
}

我的直觉说应该选择f(string &&)重载,因为arg无论如何都需要转换为临时值,并且临时匹配rvalue引用比lvalue引用更好。

这不是 GCC和 MSVC中发生的事情(编辑:谢谢Sumant:它不会发生在GCC 4.3-4.5中)。至少在 G ++和 MSVC中,任何左值都不会绑定到右值引用参数,即使还有一个中间临时创建。实际上,如果不存在const ref重载,编译器会诊断出错误。但是,编写f(arg + 0)f(std::string(arg)) 选择正确的rvalue引用重载。

从我对C ++ 0x标准的阅读中,在考虑f(string &&)是否可行时,似乎应该考虑将const char *隐式转换为字符串,就像传递const lvalue ref一样参数。第13.3节(重载解析)在很多地方没有区分rvalue refs和const引用。此外,似乎阻止左值绑定到右值引用(13.3.3.1.4 / 3)的规则如果存在中间临时值则不适用 - 毕竟,从临时值移动是完全安全的。

这是:

  1. 我误读/误解了标准,其中实现的行为是预期的行为,并且为什么我的示例应该按照它的方式行事有一些很好的理由?
  2. 编译器供应商以某种方式犯下的错误?或者是基于共同实施策略的错误?或者是例如错误GCC(首次实施此左值/右值参考绑定规则),是否被其他供应商复制?
  3. 标准中的缺陷,或意外后果,或应澄清的内容?
  4. 编辑:我有一个相关的后续问题:C++0x rvalue references - lvalues-rvalue binding

6 个答案:

答案 0 :(得分:8)

根据FCD,海湾合作委员会做错了。 FCD在8.5.3处说参考绑定

  • 如果引用是左值引用且初始化表达式是[左值/类类型] ...
  • 否则,引用应是对非易失性const类型的左值引用(即,cv1应为const),或者引用应为rvalue引用,初始化表达式应为rvalue或具有函数类型。

您调用std::string &&的情况与其中任何一个都不匹配,因为初始化程序是左值。它没有到达创建临时右值的地方,因为顶级子弹已经需要一个右值。

现在,重载解析不会直接使用引用绑定来查看是否存在隐式转换序列。相反,它在13.3.3.1.4/2

处说
  

当引用类型的参数没有直接绑定到参数表达式时,转换序列是根据13.3.3.1将参数表达式转换为引用的基础类型所需的转换序列。

因此,即使获胜者实际上无法绑定到该参数,重载决策也会成为赢家。例如:

struct B { B(int) { /* ... */ } };
struct A { int bits: 1; };

void f(int&);
void f(B);
int main() { A a; f(a.bits); }

8.5处的引用绑定禁止位域绑定到左值引用。但是重载分辨率表明转换序列是转换为int的转换序列,因此即使稍后进行调用时,转换也是成功的。因此,我的位域示例是不正确的。如果要选择B版本,它会成功,但需要用户定义的转换。

但是,该规则存在两个例外情况。这些是

  

除了隐式对象参数(参见13.3.1)之外,如果要求将非const的左值引用绑定到右值或将左值引用绑定到左值,则无法形成标准转换序列。

因此,以下调用有效:

struct B { B(int) { /* ... */ } };
struct A { int bits: 1; };

void f(int&); /* binding an lvalue ref to non-const to rvalue! */
void f(B);
int main() { A a; f(1); }

因此,您的示例调用const T&版本

void f(const std::string &);
void f(std::string &&); // would bind to lvalue!

void g(const char * arg) { f(arg); }

但是,如果您说f(arg + 0),则创建一个右值,因此第二个函数是可行的。

答案 1 :(得分:6)

这是您阅读的标准草案中的缺陷。出于安全原因,这种缺陷是由于某些急切编辑的副作用而禁止将左值引用绑定到左值。

你的直觉是正确的。当然,即使初始化程序是左值表达式,允许rvalue引用引用一些未命名的临时也没有什么坏处。毕竟,这是rvalue引用的用途。您观察到的问题已于去年修复。即将推出的标准将强制要求在您的示例中选择第二个重载,其中rvalue引用将引用一些临时字符串对象。

规则修正案已纳入草案n3225.pdf(2010-11-27):

  
      
  • [...]
  •   
  • 否则,引用应该是对非易失性const类型的左值引用(即,cv1应为const),或者引用应为右值引用,并且初始化表达式应为rvalue或具有功能类型。 [...]      
        
    • [...]
    •   
    • 否则,会创建一个临时的[...]
    •   
  •   
        double&& rrd3 = i; // rrd3 refers to temporary with value 2.0

但是N3225似乎错过了在这个例子中说i的内容。最新的N3290草案包含以下示例:

        double d2 = 1.0;
        double&& rrd2 = d2; // error: copying lvalue of related type
        int i3 = 2;
        double&& rrd3 = i3; // rrd3 refers to temporary with value 2.0

由于您的MSVC版本在此问题得到修复之前发布,因此它仍然根据旧规则处理rvalue引用。下一个MSVC版本应该实现新的右值引用规则(MSVC开发人员称为“rvalue references 2.1”)see link

答案 2 :(得分:2)

我没有看到Doug在g ++上提到的行为。 g ++ 4.5和4.4.3都按预期调用f(string &&),但VS2010调用f(const string &)。您使用的是哪个g ++版本?

答案 3 :(得分:1)

如果你问我,目前标准草案中的很多内容都需要澄清。编译器仍在开发中,因此很难相信他们的帮助。

看起来非常清楚你的直觉是对的......任何类型的临时工都应该绑定到右值参考。例如,§3.10,新的“分类法”部分,将临时定义为rvalues。

问题可能是RR参数规范不足以调用临时的创建。 §5.2.2/ 5:“如果参数是const引用类型,则在需要时引入临时对象。”这听起来很奇怪。

似乎在§13.3.3.1/ 6中再次滑过裂缝:(强调我的)

  

当参数类型不是引用时,隐式转换序列模拟参数表达式中参数的复制初始化。隐式转换序列是将参数表达式转换为参数类型的prvalue所需的序列。

请注意,复制初始化string &&rr = "hello";在GCC中可以正常工作。

编辑:实际上我的GCC版本上不存在这个问题。我仍在试图弄清楚用户定义的转换序列的第二个标准转换如何与形成右值引用相关。 (RR形成是一种转换吗?还是由像5.2.2 / 5这样的散乱花絮所决定?)

答案 4 :(得分:0)

看看这个:

http://blogs.msdn.com/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-part-2.aspx

右值参考:重载决议

看起来你的情况是:“Lvalues强烈倾向于绑定到左值引用”。

答案 5 :(得分:0)

我不知道这个标准的最新版本是否有所改变,但过去曾经说过“如果有疑问,请不要使用rvalue参考”。可能出于兼容性原因。

如果您想要移动语义,请使用适用于两个编译器的f(std::move(arg))