为什么将“istream&”放入临时“stringstream”可以工作,但在使用“stringstream&”时却不起作用?

yep*_*ons 7 c++ rvalue

考虑以下代码,它编译并运行:

#include <iostream>
#include <sstream>
struct Foo {};
void operator>>(std::istream &, Foo) {
}
int main() {
    std::stringstream{} >> Foo{};
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我更改std::istreamstd::stringstream,则会出现错误:

c.cpp: In function 'int main()':
c.cpp:7:25: error: no match for 'operator>>' (operand types are 'std::stringstream' {aka 'std::__cxx11::basic_stringstream<char>'} and 'Foo')
    7 |     std::stringstream{} >> Foo{};
      |          ~~~~~~~~~~~~~~ ^~ ~~~~~
      |          |                 |
      |          |                 Foo
      |          std::stringstream {aka std::__cxx11::basic_stringstream<char>}
c.cpp:4:6: note: candidate: 'void operator>>(std::stringstream&, Foo)' (near match)
    4 | void operator>>(std::stringstream &, Foo) {
      |      ^~~~~~~~
c.cpp:4:6: note:   conversion of argument 1 would be ill-formed:
c.cpp:7:10: error: cannot bind non-const lvalue reference of type 'std::stringstream&' {aka 'std::__cxx11::basic_stringstream<char>&'} to an rvalue of type 'std::stringstream' {aka 'std::__cxx11::basic_stringstream<char>'}
    7 |     std::stringstream{} >> Foo{};
      |          ^~~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)

这是有道理的:我无法将左值引用绑定到右值(临时)对象。

为什么第一段代码可以编译?

UPD:我的编译器是

g++ (Rev2, Built by MSYS2 project) 10.3.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Run Code Online (Sandbox Code Playgroud)

和标志是-std=gnu++17 -Wall -Wextra -Wshadow -O2

UPD2:最后有一个关于 Brian Bi 正在谈论的运算符的错误:

In file included from C:/Software/msys64/mingw64/include/c++/10.3.0/iostream:40,
                 from c.cpp:1:
C:/Software/msys64/mingw64/include/c++/10.3.0/istream:980:5: note: candidate: 'template<class _Istream, class _Tp> typename std::enable_if<std::__and_<std::__not_<std::is_lvalue_reference<_Tp> >, std::__is_convertible_to_basic_istream<_Istream>, std::__is_extractable<typename std::__is_convertible_to_basic_istream<_Tp>::__istream_type, _Tp&&, void> >::value, typename std::__is_convertible_to_basic_istream<_Tp>::__istream_type>::type std::operator>>(_Istream&&, _Tp&&)'
  980 |     operator>>(_Istream&& __is, _Tp&& __x)
      |     ^~~~~~~~
C:/Software/msys64/mingw64/include/c++/10.3.0/istream:980:5: note:   template argument deduction/substitution failed:
C:/Software/msys64/mingw64/include/c++/10.3.0/istream: In substitution of 'template<class _Istream, class _Tp> typename std::enable_if<std::__and_<std::__not_<std::is_lvalue_reference<_Tp> >, std::__is_convertible_to_basic_istream<_Istream>, std::__is_extractable<typename std::__is_convertible_to_basic_istream<_Tp>::__istream_type, _Tp&&, void> >::value, typename std::__is_convertible_to_basic_istream<_Tp>::__istream_type>::type std::operator>>(_Istream&&, _Tp&&) [with _Istream = std::__cxx11::basic_stringstream<char>; _Tp = Foo]':
c.cpp:7:32:   required from here
C:/Software/msys64/mingw64/include/c++/10.3.0/istream:980:5: error: no type named 'type' in 'struct std::enable_if<false, std::basic_istream<char>&>'
Run Code Online (Sandbox Code Playgroud)

Bri*_*ian 12

尽管看起来很奇怪,但这段代码的格式良好。它应该总是编译。参见神箭operator>>当重载更改为 take时,它​​也应该编译std::stringstream&

原因是operator>>从 派生的类存在特殊的右值重载std::ios_base此处标记为 (3) :

template< class Istream, class T >
Istream&& operator>>( Istream&& st, T&& value );
Run Code Online (Sandbox Code Playgroud)

效果相当于:

st >> std::forward<T>(value);
return std::move(st);
Run Code Online (Sandbox Code Playgroud)

这是operator>>您的代码调用的重载。它委托给operator>>您所写的内容。(因为Foo是全局命名空间的成员,因此operator>>借助 ADL,非限定名称查找可以从任何上下文中找到您的名称。)

如果您的工具链没有右值流提取运算符,那么您的代码将无法编译,因为显然,非常量左值引用Base&永远不会绑定到类型的右值Derived(其中Derived派生自Base)。

我不知道为什么标准库中存在右值流提取运算符。我认为这是因为有人意识到如果恰好是流类型的右值表达式,则没有充分的理由is >> x不工作。is但许多现有的operator>>s 都是自由函数,将左值引用作为basic_istream第一个参数。因此,添加此右值operator>>重载(委托给左值重载)就是解决此问题的方法。根据这个观点,您的代码编译的事实并不是一个错误:您有意编写一个operator>>接受流类型的非常量左值引用并使其也适用于右值的代码。