14

(我在 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 中发生的(编辑:感谢 Sumant:它不会在 GCC 4.3-4.5 中发生)。至少在G++ 和MSVC 中,任何左值都不会绑定到右值引用参数,即使创建了中间临时值也是如此。实际上,如果不存在 const ref 重载,编译器会诊断出错误。但是,如您所料,编写f(arg + 0) orf(std::string(arg)) 确实选择了右值引用重载。

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

这是:

  1. 我误读/误解了标准,其中实现的行为是预期的行为,并且有一些很好的理由说明我的示例应该按照它的方式运行?
  2. 编译器供应商都犯了一个错误?还是基于常见实施策略的错误?或者例如 GCC 中的一个错误(这个左值/右值引用绑定规则是第一次实现的),被其他供应商复制了?
  3. 标准中的缺陷,或意外后果,或应该澄清的东西?

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

4

6 回答 6

8

根据 FCD,GCC 做错了。FCD 说8.5.3关于参考绑定

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

您调用std::string &&匹配项的案例都不匹配,因为初始化程序是lvalue。它无法创建临时右值,因为该顶级项目符号已经需要一个右值。

现在,重载解析不直接使用引用绑定来查看是否存在隐式转换序列。相反,它说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),则创建了一个右值,因此第二个函数是可行的。

于 2010-05-01T10:38:45.630 回答
6

这是您阅读的标准草案中的一个缺陷。这个缺陷是一些急切的编辑的副作用,出于安全原因,不允许将右值引用绑定到左值。

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

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

  • [...]
  • 否则,该引用应为对非易失性 const 类型的左值引用(即 cv1 应为 const),或者该引用应为右值引用且初始化表达式应为右值或具有函数类型。[...]
    • [...]
    • 否则,将创建 [...] 的临时 [...]
        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”)见链接

于 2011-08-29T13:08:45.360 回答
2

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

于 2010-05-01T04:57:07.460 回答
1

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

很明显,您的直觉是正确的……任何类型的临时对象都应该绑定到右值引用。例如,第 3.10 节,新的“分类法”部分,将临时变量明确定义为右值。

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

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

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

请注意,复制初始化string &&rr = "hello";在 GCC 中运行良好。

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

于 2010-05-01T04:32:36.523 回答
0

看看这个:

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

右值引用:重载决议

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

于 2010-05-01T04:53:22.627 回答
0

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

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

于 2010-05-01T05:06:11.753 回答