1

I want to code another stream class. Done it before like this:

class MyStream
{
   // ...
};

template <typename T>
MyStream& operator <<(MyStream& s, const T& t) noexcept
{
   std::cout << t;
   return s;
}

void f()
{
   MyStream s;
   s << 666;
}

This time I need to make it work with temporaries as well:

void f()
{
   MyStream() << 666;

   MyStream s;
   s << 777;
}

I understood that this can be solved using an rvalue reference:

template <typename T>
MyStream& operator <<(MyStream&& s, const T& t) noexcept
{
   std::cout << t;
   return s;
}

However I do not understand the implications of doing so.

Should I implement a conventional lvalue reference operator as well? Is there anything that could go wrong? Is it ok to convert a rvalue reference into a lvalue reference like this?

Silicomancer
  • 8,604
  • 10
  • 63
  • 130

1 Answers1

-1

Never turn an rvalue into an lvalue. This is one the most common causes of dangling reference. You could return an rvalue reference but that is already a mistake:

template <typename T>
MyStream&& operator <<(MyStream&& s, const T& t) noexcept
{
   std::cout << t;
   return std::move(s);
}

void foo(){
    auto && o = Mystream{} << 12;
    o << "x" ; //undefined behavior
    }

The moral is never return an rvalue reference and never turn an rvalue into an lvalue. The corollary is always ref qualify assignment operators and pre-increment/pre-decrement operators. Those who are not able to follow this simple discipline should switch to Rust as it does not offer the opportunity to such mistakes.

Silicomancer
  • 8,604
  • 10
  • 63
  • 130
Oliv
  • 17,610
  • 1
  • 29
  • 72
  • This is interesting but does not answer my question, does it? Do you have a clean solution for what I want to do? Also, could you elaborate a bit, why your code example triggers UB? – Silicomancer Jul 20 '20 at 15:44
  • 1
    Absolutely nothing whatsoever in the whole god's green world can turn an rvalue into an lvalue. An rvalue reference OTOH can be turned into an lvalue reference. This is not a problem by itself. Any code that returns (or consumes) a reference can be abused. Just don't abuse it. – n. m. could be an AI Jul 20 '20 at 18:06
  • @Silicomance Because *temporaries* are destroyed at the end of *full-expression*. – Oliv Jul 20 '20 at 18:36
  • @n.'pronouns'm. lvalue or rvalue are expressions that determines the identity of an object. As such they denote an objects as could a name an entity. As usual in language when one use a name, he is not talking about the name but what the name denotes, this is precisely what I did. On the other hand, rvalue reference or lvalue reference are types genera. If you are not a philistine you certainly know that expression cannot have reference type. So now you hopefully understand that an rvalue reference can not be turned into anything because rvalue reference does not denote an entity. – Oliv Jul 20 '20 at 18:57
  • Of course a reference is an entity, see 6 basics. An expression can have a reference type, though it is adjusted to a non-reference type before further analysis. Anyway rvalues are bound to (const) lvalue references all the time. – n. m. could be an AI Jul 21 '20 at 05:00
  • @n.'pronouns'm I could not imagine you were talking about reference as entity. When you refer to something as an entity you refer to its identity: what in this something is constant and cannot be changed! So definitively a rvalue reference cannot be turned into an lvalue reference. But an object can be denoted either by an rvalue or an lvalue, which somehow change its conotation. – Oliv Jul 21 '20 at 08:19
  • BTW It turned out that the standard library does what I did to enable stream operators on temporary stream objects (i.e. using a && parameter and return it as &). – Silicomancer Jul 21 '20 at 08:57
  • No need to imagine, just read the standard, the section I mentioned (6 basic). – n. m. could be an AI Jul 21 '20 at 12:00
  • @Silicomancer The standard library opertor free functions reproduce member function operator behavior that predates C++11. With C++11, was introduced member function reference qualifier. Then there was a debate about making some member function ref qualified. It was recognize as being the best approach but it would have broken retro compatibility. Your helders have recognized the mistake but kept it around for retrocompatibility. But there are no reason on earth to reproduce this mistake in new project. Unfortunately, I have no authority so I just can hope you will learn C++ before coding – Oliv Jul 21 '20 at 12:16
  • @Oliv I am totally okay with ref qualified member operators. Could you tell me how to add operator overloads in separate modules for custom types using that pattern? Also, do you have a link or something that explains in detail in which ways the current std. lib. solution is bad? – Silicomancer Jul 21 '20 at 20:35