C++ should support temporary objects as lvalues

Microsoft Visual C++ has long implemented an extension to C++ that the standard language does not permit: temporary objects can be assigned to non-const references (lvalues).

I find this extension useful, allowing for some elegant constructs that standard C++ does not permit.

Example 1:

void StrCvtCp(Seq in, Str& out, UINT inCp, UINT outCp, Vect<wchar_t>& convertBuf);

Here I have a function that converts a string from one code page into another, using MultiByteToWideChar and WideCharToMultiByte internally. The function needs to use a conversion buffer. For callers that might call this function many times in a row, it is more efficient to keep reusing the same conversion buffer than to reallocate it in every call to the function. More occasional callers, however, don't want the inconvenience of having to instantiate the buffer, and just want one to be created by default.

In MSVC, solving this is easy:

void StrCvtCp(Seq in, Str& out, UINT inCodePage, UINT outCodePage,
    Vect<wchar_t>& convertBuf = Vect<wchar_t>());

But in standard C++, I must do this:

void StrCvtCp(Seq in, Str& out, UINT inCp, UINT outCp, Vect<wchar_t>& convertBuf);

inline void StrCvtCp(Seq in, Str& out, UINT inCp, UINT outCp)
    { Vect<wchar_t> convertBuf; StrCvtCp(in, out, inCp, outCp, convertBuf); }

Ugh. So much more typing, and more opportunity for error.

Example 2:

In MSVC, I can do this:

smtpMsg->f_content.Reserve(1000 + plainContent.Len());

But in standard C++, I must do this:

smtpMsg->f_content.Reserve(1000 + plainContent.Len());
Imf::MsgWriter msgWriter(smtpMsg->f_content);

Why? Just... why? I have no reason to name that temporary.

Example 3:

I have a class Console, which defines this:

struct Console

    template <Device D>
    struct WriteOnDestroy : Str
        WriteOnDestroy() = default;
            { if (!std::uncaught_exception() && !Empty()) Console::Write(D, *this); }
        WriteOnDestroy(WriteOnDestroy const&) = delete;
        void operator=(WriteOnDestroy const&) = delete;

    using Out = WriteOnDestroy<StdOut>;
    using Err = WriteOnDestroy<StdErr>;

I want to use it like this:

    << e.what() << "\r\n"
       "- Run without parameters to start service in console\r\n"
       "- Use parameter 'register' or 'unregister' to register/unregister service\r\n";

In MSVC, this works because the line Console::Out() instantiates a temporary object, which derives from Str, and can be passed to operator << overloads like this one:
inline Str& operator<< (Str& s, char const* z) { return s.Add(z); }

There's a substantial reason I want to use this temporary object: by concatenating strings and deferring the write until the temporary object's destructor, I provide implicit thread safety by only making one call to the Console::Write method.

In standard C++, this doesn't work, because the temporary object cannot be used as an lvalue. I must instead define the following monstrosity:

template <typename T>
Console::Out const& operator<< (Console::Out const& x, T&& a)
    { ((Str&) x) << std::forward<T>(a); return x; }

This works, but Jesus, look at the abuse it's doing.


When I proposed the above on Reddit, the most popular response was that it's to prevent this problem:

void increment(long& foo) { foo += 1; }
int main() {
    int x = 123;
    increment(x);      // Temporary created
    assert(x == 124);  // Oops!

But this is a strawman. MSVC does not accept this:

error C2664: 'void increment(long &)' : cannot convert argument 1 from 'int' to 'long &'

Even though it does support temporary objects as rvalues, the temporary object has to be explicit.

I haven't been able to cause a problem like this with classes, either. As far as I can tell, MSVC won't implicitly create a temporary to assign to a non-const ref. However, an explicitly created temporary works.

I see no reason it shouldn't work in standard C++.


Popular posts from this blog

When monospace fonts aren't: The Unicode character width nightmare

Circumcision as an adult, part 1

Circumcision as an adult, part 2