Search code examples
c++referenceundefined-behavior

Passing non-lvalue as const reference argument. Is the temp created in local scope or caller scope?


Ok, so, I already know that returning a local variable as reference will cause undefined behavior when we try to use it and that we can create a non-const reference to only form a lvalue variable. Creating a const reference does not need to be created from a lvalue variable, because if it is created from a non-lvalue variable, it creates a temporary one in that scope (code below).

const int& refa {2};

// basically equals
const int temp_a{2};
const int& refa{temp_a};

And there goes my question, if we pass a non-lvalue as a parameter to a function that takes a const reference, will the temporary variable be created in local scope of the caller or in local scope of the function? I am curious if returning the argument as reference and using it will cause undefined behavior. Sample (assume that the method is not inline):

_declspec(noinline) const int& inMax(const int& a, const int& b)
{
     return a > b ? a : b;
}

int main()
{
    int a{ 5 }, b{ 6 };

    // Will the temporary variables for POST decrementation/incrementation result 
    // will be created here or inside inMax(...)
    const int& ret = inMax(a++, b--);
    // is the ret safe to use?
}

Solution

  • It will be created in the scope of the calling function (the called function can't tell if it has been passed an rvalue it needs to destruct, or a real lvalue). That's the good news. The bad news is that the temporary will be destroyed at the end of the full expression which includes the function call.

    The compiler will transform your code into something like:

    const int& ret; // Not yet initialized
    {
        const int tempA = a++;
        const int tempB = b--;
        ret := inMax(tempA, tempB); // Initialize ret with reference to tempA or tempB
        // destroy tempB, and tempA
    }
    // ret is now a dangling reference.
    

    However. Unlike the "return referencee to local variable" case, it is valid to use the function result in the statement which contains the function call. Thus:

        int ret = inMax(a++,b--);
    

    is valid. In this case, ret is copy constructed from the reference - which is not yet dangling. Also, if the function returns a reference to a class object, it is safe to call a member of that class in the same statement.

    Having said all that, this is highly risky territory. If someone comes along later and splits a long complex statement into several parts, they can introduce undefined behaviour.