Search code examples
c++referencetemporary-objects

std::make_unique as workaround for temporary object not binding to non const reference in C++


In the code snippet below, the call mc.function1() succeeds with default value being passed to function1.

I am wondering how the visual studio does not consider *std::make_unique< SomeClass >() as temporary object. Where as, if I set SomeClass() as default value, then visual studio correctly catches it as compile time error, since it is a temporary object which we are trying to bind to non const reference.

Why does Visual Studio 2019 considers *std::make_unique< SomeClass >() as non temporary object?

According to C++ ISO standards, is *std::make_unique< SomeClass >() considered as temporary object?

Or is compiler simply being lenient with it?

class SomeClass
{
};

class MyClass
{
public:

    void function1 (SomeClass &obj = *std::make_unique<SomeClass>()) { }
    //void function2 (SomeClass &obj = SomeClass()) { } //compile error
};

int main()
{
    MyClass mc;
    mc.function1();
}

function1 below compiles and executes fine.

void function1 (SomeClass &obj = *std::make_unique<SomeClass>()) { }

I think it should not compile, since *std::make_unique() is temporary object which is being bound to non const reference. But it builds and runs fine.


Solution

  • Whether or not an object can be bound to a reference has nothing to do with whether or not it is a temporary object. It is purely a matter of what the value category of the expression used to initialize the reference is. If the value category is lvalue, then the lvalue reference can be bound to the object that the expression refers to.

    operator* of std::unique_ptr has a lvalue reference as return type. A function call to a function returning a lvalue reference is an lvalue. So a lvalue reference can be bound to the object that the result of the * refers to.

    std::unique_ptr is not special in any way here. You can write your own function that will do it:

    template<typename T>
    T& as_lvalue(T&& t) {
        return t;
    }
    

    Now you can write

    SomeClass& x = as_lvalue(/*whatever*/);
    

    It doesn't matter what value category /*whatever*/ has, the reference can now bind to it.

    The rule that you can't bind a rvalue to a non-const lvalue reference is there purely as a matter to protect you, because most of the time it is semantically wrong to do so and/or there are clearer alternatives (e.g. const lvalue reference or rvalue reference). If you want to ignore that protection, C++ won't stop you.

    But consider whether you really want to do that. Even if you manage to bind a reference to a temporary object, that generally won't extend the lifetime of the temporary. For example if /*whatever*/ above refers to a temporary object, the lifetime of that temporary object will end after the line initializing the reference. So the reference will immediately be dangling.

    Similarly in your example the temporary object created with std::make_unique will live only until the end of the function call. If you store the reference somewhere else or return it from the function, it will be dangling. So, what is the point of being able to modify it through the reference (non-const lvalue reference), if the caller can never use the value your store into it? It can just be a const lvalue reference instead.