Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does taking `istream&` to a temporary `stringstream` work, but not when taking `stringstream&`?

Tags:

c++

rvalue

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>&>'
like image 595
yeputons Avatar asked Sep 24 '21 20:09

yeputons


People also ask

What is the function of istream?

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..

How does istream work C++?

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 ).

How do I read istream?

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() .

What is difference between Ifstream and istream?

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.


Video Answer


1 Answers

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.

like image 85
Brian Bi Avatar answered Oct 18 '22 15:10

Brian Bi