10

I built this code with gcc/clang and got different results:

#include <iostream>
#include <sstream>

int main() {
    std::istream& is = 1 ? std::move(std::stringstream("")) : std::cin;
}
  • Why gcc allows initializing lvalue reference with rvalue (std::stringstream(""))?
  • Why clang tries to call copy constructor?

gcc 4.9.1

No error

clang 3.4

prog.cc:5:63: error: call to implicitly-deleted copy constructor of 'istream' (aka 'basic_istream<char>')
    std::istream& is = 1 ? std::move(std::stringstream("")) : std::cin;
                                                             ^~~~~~~~
/usr/local/libcxx-3.4/include/c++/v1/istream:185:5: note: copy constructor is implicitly deleted because 'basic_istream<char, std::__1::char_traits<char> >' has a user-declared move constructor
   basic_istream(basic_istream&& __rhs);
   ^
prog.cc:5:28: error: calling a protected constructor of class 'std::__1::basic_istream<char, std::__1::char_traits<char> >'
   std::istream& is = 1 ? std::move(std::stringstream("")) : std::cin;
                          ^
/usr/local/libcxx-3.4/include/c++/v1/istream:185:5: note: declared protected here
   basic_istream(basic_istream&& __rhs);
   ^
prog.cc:5:28: error: calling a protected constructor of class 'std::__1::basic_istream<char, std::__1::char_traits<char> >'
   std::istream& is = 1 ? std::move(std::stringstream("")) : std::cin;
                          ^
/usr/local/libcxx-3.4/include/c++/v1/istream:185:5: note: declared protected here
   basic_istream(basic_istream&& __rhs);
   ^
5gon12eder
  • 24,280
  • 5
  • 45
  • 92
h2so5
  • 340
  • 2
  • 12
  • I know what clang is doing, and it's correct. I have no idea what GCC is doing. – T.C. Jan 10 '15 at 18:45
  • 1
    Seems to be the `std::move` that confuses GCC. Without it, it will give a proper error. This is strange because the `std::move` is actually redundant here anyway. – 5gon12eder Jan 10 '15 at 19:02

1 Answers1

6

GCC's behavior is a bug, and it's been fixed on trunk. Clang is correct. This is a messy case because you have mixed value categories for the second and third operands of the conditional operator:

  • std::move(std::stringstream("")) is an xvalue* of type std::stringstream;
  • std::cin is an lvalue of type std::istream.

The relevant standard quote (§5.16 [expr.cond]/p3-6) can be found in this answer. It's long enough that I don't really want to copy it over. I'll just outline how it is applied to this code:

  • Obviously std::istream cannot be converted to match std::stringstream in any way regardless of value category;
  • An xvalue of type std::stringstream cannot be converted to type "lvalue reference to std::istream" given the constraint that the reference must bind directly to an lvalue - there's no lvalue here for the reference to bind to;
  • std::istream is a base class of std::stringstream, so per the 3rd bullet of p3, the xvalue of type std::stringstream can and will be converted to a prvalue temporary of type std::istream by copy-initialization, which replaces the original operand for further analysis.
  • Now the second operand is a prvalue of type std::istream, the third operand is an lvalue of type std::istream, they have different value categories so p4 doesn't apply.
  • Hence the result is a prvalue per p5. Since they have the same type, overload resolution specified in p5 is not performed, and you proceed to p6.
  • The applicable bullet in p6 is

    The second and third operands have the same type; the result is of that type. If the operands have class type, the result is a prvalue temporary of the result type, which is copy-initialized from either the second operand or the third operand depending on the value of the first operand.

    so it copy-initializes the result (which is a prvalue temporary) from either the converted first operand, or the second operand (std::cin).

Hence the errors:

  • Copy-initializing the prvalue std::istream result from an lvalue (std::cin) would use the copy constructor, and streams cannot be copied.
  • Copy-initializing the prvalue temporary std::istream for the second operand from a std::stringstream xvalue is a move, but std::istream's move constructor is protected.

* For terminology (lvalue, xvalue, prvalue, etc.), see What are rvalues, lvalues, xvalues, glvalues, and prvalues?

Community
  • 1
  • 1
T.C.
  • 133,968
  • 17
  • 288
  • 421