【问题标题】:Why can I std::move a stream rvalue ref into an lvalue ref?为什么我可以将流右值引用移动到左值引用中?
【发布时间】:2013-01-29 18:07:24
【问题描述】:

据我了解 C++11 引用,我不应该将右值引用绑定到(非常量)左值引用,因为前者可能绑定到临时变量,而后者绝不能绑定到临时的。

但是我发现这种奇怪的行为与临时流对象结合使用(我尽可能减少)

struct Dummy {};
template <typename Stream>
Stream& operator<<(Stream& s, Dummy) {
  return s << ".";          // <- (A)
}

template <typename Stream>
void pass(Stream&& s) {
  std::move(s) << Dummy();  // <- (X)   rvalue->lvalue conversion?
}

#include <fstream>
int main() {
  pass(std::fstream("test",std::ios::out));
}

如果我在(X) 行中写s &lt;&lt; Dummy(),C++ 会在(A) 行中抱怨,说

error: invalid initialization of reference of type ‘std::basic_fstream&lt;char&gt;&amp;’ from expression of type ‘std::basic_ostream&lt;char&gt;’

但是,为什么代码(如上所示)编译并按预期工作? std::move 返回的右值引用应该与表达式s 一样无法绑定到左值引用,但gcc 4.6.1 和gcc 4.7.2 的反应相同。

为什么这种现象似乎只适用于流?当直接将Dummy&amp;&amp; 传递给期望T&amp; 的函数时,无论有无std::move 都会失败。

【问题讨论】:

    标签: c++ c++11 iostream move-semantics rvalue-reference


    【解决方案1】:

    basic_ostream 的重载为 operator&lt;&lt;,如下所示:

    template <typename Elem, typename Traits, typename T>
    basic_ostream<Elem, Traits>&
        operator<<(basic_ostream<Elem, Traits>&& sink, const T& val)
    {
        return sink << val;
    }
    

    这在标准中称为“右值流插入”,第 27.7.3.9 节 [ostream.rvalue]。

    它允许从右值basic_ostream 到左值的隐式转换(排序)。专门引入它是为了允许temporary streams to be usable without resorting to tricks。


    至于为什么省略移动时编译失败:

    当Stream&amp; operator&lt;&lt;(Stream&amp; s, Dummy) 被调用时,Stream 将是std::fstream,它继承自std::ostream(即basic_ostream&lt;char&gt;)。

    它将使用basic_ostream&lt;E, T&gt;&amp; operator&lt;&lt;(basic_ostream&lt;E, T&gt;&amp;, const char*) 重载来插入您的字符串,然后尝试返回该表达式的结果这将是一个ostream。您不能从std::ostream&amp; 隐式向下转换为std::fstream&amp;,因此会出现错误。

    您可以通过在其自己的行上返回 s 来解决此问题(它不会被隐式向上转换。)

    这对于 move 来说不是问题,因为您通过了我们刚刚发现的右值到左值插入运算符。在该函数内部,流是basic_ostream,Stream 也是,返回类型将匹配。

    【讨论】:

    • 我明白了。这确实有道理。但那为什么还需要std::move呢?
    • @bitmask:因为没有它s 不能绑定到右值引用,因为它是一个左值。
    • @KerrekSB:但是对于要绑定到的右值引用流来说,它有一个非常好的重载(如this answer所示)。如果没有std::move 的鼓励,为什么这种超载不匹配?
    • @bitmask:这还不够。查看this example:如果删除std::move,它将不再编译。 (但我错过了由于返回类型而导致的低调的重要一点。您可以尝试更改运算符以包含static_cast&lt;Stream&amp;&gt;。)
    • @Silicomancer 我不确定。我也听说过这些说法,但还没有看到具体的原因。我确信这可能会在“可能”这个词的某种意义上引起问题(例如,一个人为的例子),但我希望这或多或少只适用于典型代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-15
    • 2022-11-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多