8

I have found this answer to the question "Does a const reference prolong the life of a temporary?", which states:

Only local const references prolong the lifespan.

I'm afraid my standardese is not up to scratch to know whether foo, below, is a local const reference or not.

Does my const std::string& foo below prolong the lifetime of the temporary std::string function argument created in the call to get_or, or do I have a dangling reference?

#include <iostream>
#include <boost/optional.hpp>

struct Foo
{
    const std::string& get_or(const std::string& def)
    {
        return str ? str.get() : def;
    }

    boost::optional<std::string> str;
};

int main()
{
    Foo f;
    const std::string& foo = f.get_or("hello world");

    std::cout << foo << '\n';
}
Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213
  • 2
    The accepted answer to the question you reference (which quotes the standard) explicitly says two cases where this doesn't work. Your example is the second case: A temporary bound to a reference parameter in a function call. So the answer is no. – Anon Mail May 31 '17 at 20:47

3 Answers3

4

const& won't extend lifetimes in that situation. Consider the example here that constructs a temporary and then attempts to print it: it's using the same constructs as your code, but I've altered it to make object construction and destruction more explicit to the user.

#include <iostream>

struct reporting {
    reporting() { std::cout << "Constructed" << std::endl;}
    ~reporting() { std::cout << "Destructed" << std::endl;}
    reporting(reporting const&) { std::cout << "Copy-Constructed" << std::endl;}
    reporting(reporting &&) { std::cout << "Move-Constructed" << std::endl;}
    reporting & operator=(reporting const&) { std::cout << "Copy-Assigned" << std::endl; return *this;}
    reporting & operator=(reporting &&) { std::cout << "Move-Assigned" << std::endl; return *this;}

    void print() const {std::cout << "Printing." << std::endl;}
};

const reporting& get_or(const reporting& def)
{
    return def;
}

int main()
{
    const reporting& foo = get_or(reporting{});

    foo.print();
    return 0;
}

Output:

Constructed
Destructed
printing.

Note how the object is destroyed before printing. is displayed.

You might be wondering why the code still completes with no visible errors: it's the result of Undefined Behavior. The object in question doesn't exist, but because it doesn't depend on state to invoke its method, the program happens to not crash. Other, more complicated examples should carry no guarantee that this will work without crashing or causing other, unexpected behavior.

Incidentally, things are a little different if the temporary is bound directly to the const&:

#include <iostream>

struct reporting {
    reporting() { std::cout << "Constructed" << std::endl;}
    ~reporting() { std::cout << "Destructed" << std::endl;}
    reporting(reporting const&) { std::cout << "Copy-Constructed" << std::endl;}
    reporting(reporting &&) { std::cout << "Move-Constructed" << std::endl;}
    reporting & operator=(reporting const&) { std::cout << "Copy-Assigned" << std::endl; return *this;}
    reporting & operator=(reporting &&) { std::cout << "Move-Assigned" << std::endl; return *this;}

    void print() const {std::cout << "printing." << std::endl;}
};

const reporting& get_or(const reporting& def)
{
    return def;
}

int main()
{
    const reporting& foo = reporting{};

    foo.print();
    return 0;
}

Output:

Constructed
printing.
Destructed

See how the object isn't destroyed until after it is used. In this situation, the object survives until the end of scope.

user2357112
  • 260,549
  • 28
  • 431
  • 505
Xirema
  • 19,889
  • 4
  • 32
  • 68
2

You passed the string through too many references.

Binding the temporary string to the def parameter of get_or extends the lifetime of the string to the end of the full expression containing the function call, but binding def to the return value of get_or and binding the return value of get_or to foo do not extend the lifetime further. The string is dead by the time you try to print it.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • In the linked answer, one of the comments refers to [this GOTW](https://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/) which says it's safe to bind a const-ref to a temporary return value. In my case, the fact that the return value is a temporary *from a parameter* makes this not apply? – Steve Lorimer May 31 '17 at 20:50
  • @SteveLorimer: Assuming you're talking about Q1 in that link, `f` returns a `string`, not a reference. Your `get_or` returns a reference, and the referent of that reference does not get its lifetime extended by binding it to more references. – user2357112 May 31 '17 at 20:52
1

The "temporary" in question is the std::string-object created when calling get_or with a parameter of type const char*. The lifetime of this temporary object is limited with the end of function get_or, and the fact that you return a reference to this temporary and assign it afterwards does not prolong the lifetime. See the following code which uses a simple "custom" string class, which couts construction and destruction:

class MyString {
public:
    MyString (const char* str) {
        m_str = strdup(str);
        cout << "constructor MyString - '" << m_str << "'" << endl;
    }
    ~MyString() {
        cout << "destructor MyString - '" << m_str << "'" << endl;
        free(m_str);
    }
    char *m_str;
};

struct Foo
{
    const MyString& get_or(const MyString& def)
    {
        cout << "Foo::get_or with '" << def.m_str << "'" << endl;
        return def;
    }
};

int main()
{
    Foo f;
    const MyString& foo = f.get_or("hello world");
    cout << "usage of foo?" << endl;
}

Output:

constructor MyString - 'hello world'
Foo::get_or with 'hello world'
destructor MyString - 'hello world'
usage of foo?

Note that the destructor is called before you will have the chance to use foo.

The situation is different if you assign a reference to a temporary directly. Again, the lifetime is until the end of the function main, but it will be used in main and not in any function calling main:

const MyString& foo2 = MyString("hello world2");
cout << "usage of foo..." << endl;

Then the output will be:

constructor MyString - 'hello world2'
usage of foo...
destructor MyString - 'hello world2'
Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58