Consider the following code, it compiles and runs:
#include <iostream>
#include <sstream>
struct Foo {};
void operator>>(std::istream &, Foo) {
}
int main() {
std::stringstream{} >> Foo{};
}
However, if I change std::istream
to std::stringstream
, I get an error:
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{};
| ^~~~~~~~~~~~~~
which makes sense: I cannot bind a lvalue reference to a rvalue (temporary) object.
Why does the first code compile?
UPD: my compiler is
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.
and flags are -std=gnu++17 -Wall -Wextra -Wshadow -O2
UPD2: at the very end there is an error regarding the operator which Brian Bi is talking about:
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>&>'
The istream class: This class is responsible for handling input stream. It provides number of function for handling chars, strings and objects such as get, getline, read, ignore, putback etc..
Extracts and parses characters sequentially from the stream to interpret them as the representation of a value of the proper type, which is stored as the value of val . Internally, the function accesses the input sequence by first constructing a sentry object (with noskipws set to false ).
Other ways to read a std::istreamTo read a line of input, try the getline() function. For this, you need #include <string> in addition to #include <iostream> . To read a single char: use the get() method. To read a large block of characters, either use get() with a char[] as the argument, or use read() .
ifstream is an input file stream. It is a special kind of an istream that reads in data from a data file. ofstream is an output file stream. It is a special kind of ostream that writes data out to a data file.
As strange as it might seem, this code is well-formed. It should always compile. See Godbolt. It should also compile when the operator>>
overload is changed to take std::stringstream&
.
The reason is that there exists a special rvalue operator>>
overload for classes derived from std::ios_base
, marked (3) here:
template< class Istream, class T >
Istream&& operator>>( Istream&& st, T&& value );
The effect is equivalent to:
st >> std::forward<T>(value);
return std::move(st);
This is the operator>>
overload that is called by your code. It delegates to the operator>>
that you've written. (Because Foo
is a member of the global namespace, unqualified name lookup will find your operator>>
from any context thanks to ADL.)
If you have a toolchain that doesn't have the rvalue stream extraction operator, then your code will not compile because, obviously, a non-const lvalue reference, Base&
, will never bind to an rvalue of type Derived
(where Derived
is derived from Base
).
I don't know exactly why the rvalue stream extraction operator exists in the standard library. I think it's because someone realized that there's no good reason for is >> x
not to work if is
happens to be an rvalue expression of stream type. But many of the existing operator>>
s were free functions taking an lvalue reference to basic_istream
as their first argument. So adding this rvalue operator>>
overload, which delegates to an lvalue one, was the solution to this problem. According to this point of view, the fact that your code compiles is not a bug: it's intentional that you can write an operator>>
that takes a non-const lvalue reference to stream type and have it work on rvalues too.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With