Is it ok to return default argument's value by const reference?

867 views Asked by At

Is it ok to return default argument's value by const reference like in the examples below:

https://coliru.stacked-crooked.com/a/ff76e060a007723b

#include <string>

const std::string& foo(const std::string& s = std::string(""))
{
    return s;
}

int main()
{
    const std::string& s1 = foo();
    std::string s2 = foo();

    const std::string& s3 = foo("s");
    std::string s4 = foo("s");
}
3

There are 3 answers

2
Oblivion On

It is not safe:

In general, the lifetime of a temporary cannot be further extended by "passing it on": a second reference, initialized from the reference to which the temporary was bound, does not affect its lifetime.

2
Brian Bi On

In your code, both s1 and s3 are dangling references. s2 and s4 are ok.

In the first call, the temporary empty std::string object created from the default argument will be created in the context of the expression containing the call. Therefore, it will die at the end of the definition of s1, which leaves s1 dangling.

In the second call, the temporary std::string object is used to initialize s2, then it dies.

In the third call, the string literal "s" is used to create a temporary std::string object and that also dies at the end of the definition of s3, leaving s3 dangling.

In the fourth call, the temporary std::string object with value "s" is used to initialize s4 and then it dies.

See C++17 [class.temporary]/6.1

A temporary object bound to a reference parameter in a function call (8.2.2) persists until the completion of the full-expression containing the call.

0
Guillaume Racicot On

It depends on what you do with the string afterwards.

If your question is is my code correct? then yes is it.

From [dcl.fct.default]/2

[ Example: The declaration

void point(int = 3, int = 4);

declares a function that can be called with zero, one, or two arguments of type int. It can be called in any of these ways:

point(1,2);  point(1);  point();

The last two calls are equivalent to point(1,4) and point(3,4), respectively. — end example]

So your code is effectively equivalent to:

const std::string& s1 = foo(std::string(""));
std::string s2 = foo(std::string(""));

All your code is correct, but there is no reference lifetime extension in any of these cases, since the return type is a reference.

Since you call a function with a temporary, the lifetime of the returned string won't extend the statement.

const std::string& s1 = foo(std::string("")); // okay

s1; // not okay, s1 is dead. s1 is the temporary.

Your example with s2 is okay since you copy (or move) from the temporary before the end of the satement. s3 has the same problem than s1.