Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it reasonably efficient to insert into an rvalue reference to a stream?

Tags:

c++

c++11

I made a custom stream type, call it error_stream, which derives from std::ostringstream. I also made a custom manipulator for the stream called throw_cpp_class (throw_cpp is an instance of throw_cpp_class). My goal was to have this syntax:

error_stream s;
s << "some error " << message() << throw_cpp; // throw_cpp throws an exception containing contents of the stream.

I discovered that by defining an insertion operator which takes an rvalue reference to the stream as the first operand, I can now do this:

error_stream() << "some error " << message() << throw_cpp;

The insertion operator looks like this:

error_stream& operator<<(error_stream&& s, const throw_cpp_class&)
{
    throw s.str();
    return s;
}

What's going on here? Why can I return a value of type error_stream&& where an error_stream& is required? (Does this invoke the move constructor?). Is this horribly inefficient? (Not that I really care, given that the exception should be rare).

like image 548
0xbe5077ed Avatar asked Jun 25 '13 01:06

0xbe5077ed


People also ask

Should I always use std move?

Q: When should it be used? A: You should use std::move if you want to call functions that support move semantics with an argument which is not an rvalue (temporary expression).

What is reference collapsing?

Reference collapsing is the mechanism that leads to universal references (which are really just rvalue references in situations where reference-collapsing takes place) sometimes resolving to lvalue references and sometimes to rvalue references.

When should I use move semantics?

That's what rvalue references and move semantics are for! Move semantics allows you to avoid unnecessary copies when working with temporary objects that are about to evaporate, and whose resources can safely be taken from that temporary object and used by another.

What is rvalue reference stackoverflow?

rvalue and lvalue are categories of expressions. rvalue reference and lvalue reference are categories of references. Inside a declaration, T x&& = <initializer expression> , the variable x has type T&&, and it can be bound to an expression (the ) which is an rvalue expression.


2 Answers

With this code:

error_stream& operator<<(error_stream&& s, const throw_cpp_class&)
{
    throw s.str();
    return s;
}

You can return error_stream&& s as a error_stream&, because s is an lvalue, and it is NOT an rvalue.

"What?" you ask? "But I see && right there!". This part of C++ is tricky. When you see type&& s (and type is not a template), then that means the variable is an rvalue reference, which is a reference that is "constructed from" an rvalue. But it has a name: s. And everything with a name is an lvalue. That's why you have to call std::move sometimes, because you have to let the compiler know that you want it to treat that variable as an rvalue again.

Does this invoke the move constructor?).

Nope, it simply returns a reference to the lvalue s.

Is this horribly inefficient? (Not that I really care, given that the exception should be rare).

Nope, since there's no copy nor even a move happening.


Unrelated to your actual question, most overloads for streams are:
ostream& operator<<(ostream&& s, const T&)

then that means that unless throw_cpp is the first thing streamed, your overload won't be called, because the previous thing streamed will return an ostream&, not an error_stream&&. (Note they ought to be templates, but many aren't, and it's irrelevant to the point) You'll have to cast it back to a error_stream.

Also, that's not how manipulators work. Manipulators are functions, and when you stream those functions to a stream, the stream calls the function and passes itself as a parameter, so you'd want something more like so:

template <class exception, class charT, class traits>
std::basic_ostream<charT,traits>& throw_cpp(std::basic_ostream<charT,traits>& os)
{
    error_stream& self = dynamic_cast<error_stream&>(os); //maybe throws std::bad_cast
    throw exception(self.str());
    return os; //redundant, but the compiler might not know that.
}

Here it is at work (with stringstream)

like image 116
Mooing Duck Avatar answered Oct 28 '22 01:10

Mooing Duck


T&& is an rvalue-reference, but its value category is that of a reference (an lvalue) so it can be stored inside an lvalue-reference. No move/copy constructors are called in this case either, because it is taken/returned by reference.

I wouldn't say this is inefficient in the least bit, but rather a common and idiomatic use of rvalue-references.

like image 33
David G Avatar answered Oct 28 '22 02:10

David G