Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initializing lvalue reference with rvalue

Tags:

c++

gcc

c++11

clang

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);
   ^
like image 404
h2so5 Avatar asked Jan 10 '15 18:01

h2so5


People also ask

Can you pass an lvalue to an rvalue reference?

In the example, the main function passes an rvalue to f . The body of f treats its named parameter as an lvalue. The call from f to g binds the parameter to an lvalue reference (the first overloaded version of g ). You can cast an lvalue to an rvalue reference.

Can rvalue bind to lvalue?

In this example, the rvalue reference a can be bound to the temporary initialized with the rvalue expression 2 , but the rvalue reference b cannot be bound to the lvalue expression i . You can bind the rvalue reference c to the temporary value 1.0 that is converted from the variable i . End of C++11 only.

How do you initialize const and reference member variables?

To initialize the const value using constructor, we have to use the initialize list. This initializer list is used to initialize the data member of a class. The list of members, that will be initialized, will be present after the constructor after colon. members will be separated using comma.


1 Answers

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?

like image 190
T.C. Avatar answered Oct 10 '22 14:10

T.C.