Reputation: 4765
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).
Upvotes: 5
Views: 316
Reputation: 66961
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.
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)
Upvotes: 15
Reputation: 96845
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.
Upvotes: 3