代码之家  ›  专栏  ›  技术社区  ›  Doug

C++ 0x rValk引用和临时变量

  •  14
  • Doug  · 技术社区  · 15 年前

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

    为什么打电话给 f(arg) 在此代码中,调用的const-ref重载 f ?

    void f(const std::string &); //less efficient
    void f(std::string &&); //more efficient
    
    void g(const char * arg)
    {
         f(arg);
    }
    

    我的直觉说 f(string &&) 应选择过载,因为 arg 无论什么都需要转换为临时的,临时的匹配右值引用比左值引用更好。

    这不是发生在 GCC和 MSVC(编辑:谢谢萨姆特:这在GCC4.3-4.5中没有发生)。至少在 G++和 msvc,任何左值都不会绑定到右值引用参数, 即使 已创建中间临时。实际上,如果不存在const-ref重载,编译器就会诊断出一个错误。然而,写作 f(arg + 0) f(std::string(arg)) 按预期选择右值引用重载。

    从我对C++0X标准的阅读来看,当考虑是否有一个const char到字符串的隐式转换时,应该考虑 F(字符串) 是可行的,就像传递常量lvalue ref参数一样。第13.3节(过载分辨率)没有在太多的地方区分右值引用和常量引用。此外,如果存在中间临时变量,则防止lvalues绑定到rvalue引用(13.3.3.1.4/3)的规则似乎不适用-毕竟,从临时变量移动是完全安全的。

    这是:

    1. 我误读/误解了标准,其中实现的行为是预期的行为,并且有一些很好的理由解释为什么我的示例应该这样做?
    2. 编译器供应商都犯了什么错误?还是基于常见实施策略的错误?或者是其他供应商复制的GCC(首先实现这个lvalue/rvalue引用绑定规则的地方)中的错误?
    3. 标准中的缺陷,或意外的后果,或应该澄清的东西?

    编辑:我有一个相关的后续问题: C++0x rvalue references - lvalues-rvalue binding

    6 回复  |  直到 15 年前
        1
  •  8
  •   Johannes Schaub - litb    15 年前

    根据FCD,GCC做得不对。联邦检察官说 8.5.3 关于引用绑定

    • 如果引用是左值引用,并且初始值设定项表达式是[lvalue/类类型]…
    • 否则,引用应为非易失性常量类型的左值引用(即,cv1应为常量),或者引用应为右值引用,初始值设定项表达式应为右值或具有函数类型。

    你的案子 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),如果标准转换序列需要将左值引用绑定到非常量右值或将右值引用绑定到左值,则无法形成标准转换序列。

    因此,以下调用有效:

    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) 创建一个右值,因此第二个函数是可行的。

        2
  •  6
  •   sellibitze    13 年前

    这是你读到的标准草案中的一个缺陷。由于安全原因,这种缺陷产生了一些急于编辑的副作用,即不允许将右值引用绑定到lvalues。

    你的直觉是对的。当然,即使初始值设定项是左值表达式,也不允许右值引用引用某个未命名的临时变量。毕竟,这就是右值引用的目的。你观察到的问题去年已经解决了。即将到来的标准将要求在示例中选择第二个重载,其中右值引用将引用一些临时字符串对象。

    规则修正案将其纳入草案N3225.pdf(2010-11-27):

    • […]
    • 否则,引用应为非易失性常量类型的左值引用(即,cv1应为常量),或引用应为右值引用。 初始值设定项表达式应为右值或具有函数类型 . […]
      • […]
      • 否则,将创建[…]的临时值[…]
            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版本是在修复此问题之前发布的,因此它仍然根据旧规则处理右值引用。下一个MSVC版本将实现新的右值引用规则(由MSVC开发人员称为“右值引用2.1”)。 see link

        3
  •  2
  •   Sumant    15 年前

    我没有看到道格在G++上提到的行为。G++4.5和4.4.3都调用 f(string &&) 如预期,但VS2010呼叫 f(const string &) . 您使用的是哪个G++版本?

        4
  •  1
  •   Potatoswatter    15 年前

    如果你问我的话,现行标准草案中有很多事情需要澄清。编译器还在开发中,所以很难相信他们的帮助。

    很明显,你的直觉是正确的,任何类型的临时性都应该绑定到右值引用。例如,?3.10,新的“分类法”部分,将临时值分类定义为rvalues。

    问题可能是rr参数规范不足以调用临时创建。§5.2.2/5:“如果参数为常量引用类型,则在需要时引入临时对象。”这听起来似乎是排他的。

    在第13.3.3.1/6节中似乎再次滑过裂缝:(重点是矿山)

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

    注意复制初始化 string &&rr = "hello"; 在GCC工作良好。

    编辑: 实际上,在我的GCC版本中没有这个问题。我仍在尝试找出用户定义的转换序列的第二个标准转换与形成右值引用的关系。(RR地层是一个转换吗?或者它是由5.2.2/5之类的零散的小道消息决定的?

        5
  •  0
  •   Alex F    15 年前

    看看这个:

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

    右值引用:重载分辨率

    看起来您的情况是:“lvalues非常喜欢绑定到lvalue引用”。

        6
  •  0
  •   user328543    15 年前

    我不知道在最新版本的标准中这一点是否发生了变化,但它曾经说“如果有疑问,不要使用右值引用”。可能是因为兼容性的原因。

    如果需要移动语义,请使用 f(std::move(arg)) 这两个编译器都可以使用。