为什么将 `istream&` 用于临时的 `stringstream` 有效,但在使用 `stringstream&` 时却不行?
Why does taking `istream&` to a temporary `stringstream` work, but not when taking `stringstream&`?
考虑以下代码,它编译并运行:
#include <iostream>
#include <sstream>
struct Foo {};
void operator>>(std::istream &, Foo) {
}
int main() {
std::stringstream{} >> Foo{};
}
但是,如果我将 std::istream
更改为 std::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{};
| ^~~~~~~~~~~~~~
这是有道理的:我不能将左值引用绑定到右值(临时)对象。
为什么第一个代码可以编译?
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.
并且标志是 -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>&>'
虽然看起来很奇怪,但这段代码的格式是正确的。它应该总是编译。参见 Godbolt。当 operator>>
重载更改为采用 std::stringstream&
.
时,它也应该编译
原因是 类 存在一个特殊的右值 operator>>
重载来自 std::ios_base
,标记为 (3) here:
template< class Istream, class T >
Istream&& operator>>( Istream&& st, T&& value );
效果相当于:
st >> std::forward<T>(value);
return std::move(st);
这是您的代码调用的 operator>>
重载。它委托给你写的 operator>>
。 (因为 Foo
是全局命名空间的成员,由于 ADL,非限定名称查找将从任何上下文中找到您的 operator>>
。)
如果您的工具链没有右值流提取运算符,那么您的代码将无法编译,因为很明显,非常量左值引用 Base&
永远不会绑定到右值Derived
类型(其中 Derived
派生自 Base
)。
我不知道为什么标准库中存在右值流提取运算符。我 认为 这是因为有人意识到如果 is
恰好是流类型的右值表达式,则 is >> x
没有充分的理由不工作。但是许多现有的 operator>>
都是自由函数,它们将对 basic_istream
的左值引用作为它们的第一个参数。所以添加这个右值 operator>>
重载,它委托给一个左值,是这个问题的解决方案。根据这个观点,你的代码编译的事实不是一个错误:你可以编写一个 operator>>
是有意的,它采用对流类型的非常量左值引用,并且它也适用于右值。
考虑以下代码,它编译并运行:
#include <iostream>
#include <sstream>
struct Foo {};
void operator>>(std::istream &, Foo) {
}
int main() {
std::stringstream{} >> Foo{};
}
但是,如果我将 std::istream
更改为 std::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{};
| ^~~~~~~~~~~~~~
这是有道理的:我不能将左值引用绑定到右值(临时)对象。
为什么第一个代码可以编译?
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.
并且标志是 -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>&>'
虽然看起来很奇怪,但这段代码的格式是正确的。它应该总是编译。参见 Godbolt。当 operator>>
重载更改为采用 std::stringstream&
.
原因是 类 存在一个特殊的右值 operator>>
重载来自 std::ios_base
,标记为 (3) here:
template< class Istream, class T >
Istream&& operator>>( Istream&& st, T&& value );
效果相当于:
st >> std::forward<T>(value);
return std::move(st);
这是您的代码调用的 operator>>
重载。它委托给你写的 operator>>
。 (因为 Foo
是全局命名空间的成员,由于 ADL,非限定名称查找将从任何上下文中找到您的 operator>>
。)
如果您的工具链没有右值流提取运算符,那么您的代码将无法编译,因为很明显,非常量左值引用 Base&
永远不会绑定到右值Derived
类型(其中 Derived
派生自 Base
)。
我不知道为什么标准库中存在右值流提取运算符。我 认为 这是因为有人意识到如果 is
恰好是流类型的右值表达式,则 is >> x
没有充分的理由不工作。但是许多现有的 operator>>
都是自由函数,它们将对 basic_istream
的左值引用作为它们的第一个参数。所以添加这个右值 operator>>
重载,它委托给一个左值,是这个问题的解决方案。根据这个观点,你的代码编译的事实不是一个错误:你可以编写一个 operator>>
是有意的,它采用对流类型的非常量左值引用,并且它也适用于右值。