8

I was binding some return value of a function to a const lvalue reference, but the object was deleted, before the lifetime of the const lvalue reference ended.

In the following example the Foo object is destroyed before the lifetime of foo ends:

#include <iostream>
#include <string>

struct Foo
{
    ~Foo()
    {
        std::cout << "Foo destroyed: " << name << std::endl;
    }
    std::string name;
};

Foo&& pass_through(Foo&& foo)
{
    return std::move(foo);
}

int main()
{
    const Foo& foo = pass_through({"some string"});
    std::cout << "before scope end" << std::endl;
}

The output is:

Foo destroyed: some string
before scope end

live on coliru: 1

I thought you can bind const T& to anything. Is it bad practice to return T&& and should returning by value be preferred?

I stumbled across this in the cpprestsdk here:

inline utility::string_t&& to_string_t(std::string &&s) { return std::move(s); }

https://github.com/Microsoft/cpprestsdk/blob/master/Release/include/cpprest/asyncrt_utils.h#L109

Very confusing, because the windows version of to_string_t (dispatched by preprocessor macros) returned by value:

_ASYNCRTIMP utility::string_t __cdecl to_string_t(std::string &&s);

Edit: Why does it work when passing the result of pass_through to a function taking const Foo&? The lifetime is extended in this case?

Anatolii
  • 14,139
  • 4
  • 35
  • 65
Markus
  • 592
  • 3
  • 13
  • too early in the morning to think, but the explicit destructor may prevent the move constructor from being implicitly created. https://en.cppreference.com/w/cpp/language/move_constructor – Kenny Ostrom Jul 04 '18 at 13:02
  • 9
    Lifetime extension only applies when the reference binds directly to a temporary. – T.C. Jul 04 '18 at 13:06
  • @KennyOstrom I tried with explicitly generating the default move constructor via `Foo(Foo&&) = default;` and it produced the same result. – Markus Jul 04 '18 at 13:06
  • Why would the lifetime of a foo-reference exceed the line const Foo& foo = pass_through({"some string"}); ? –  Jul 04 '18 at 13:15
  • Imagine if you only had the declaration of `pass_through()` available. There's no way to know there's a link between the parameter RValue ref and the result RValue ref, and the lifetime of the temporary `Foo` must still be resolved at **compile time**, so the compiler has no choice but to be pessimistic about it. –  Jul 04 '18 at 13:30
  • 2
    You can pass it as an argument because the lifetime of a temporary lasts until the end of the "full-expression" it's created in. (Very informally, "until the next semicolon".) No extension is necessary. – molbdnilo Jul 04 '18 at 13:34
  • @Pi -- in `const Foo& foo = Foo():` the lifetime of the temporary object created by `Foo()` is extended, so it remains valid as long as `foo` does. That's called "lifetime extension". This question involves creating a temporary `Foo` object indirectly, and, as comments have said, that doesn't result in lifetime extension. – Pete Becker Jul 04 '18 at 15:19
  • 1
    Static analysis ought to be able to detect this, I predict we will see compilers able to warn about this in future – M.M Jul 04 '18 at 22:04

1 Answers1

3

From the standard:

15.2 Temporary objects

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

Essentially what it's saying is that because you passed in a temporary object and then didn't extend its lifetime (say, by moving it into an lvalue) then its lifetime only lasts until the first ; after the call to pass_through in your code. After this point you are left with foo as a dangling reference.

int main()
{
    const Foo& foo = pass_through({"some string"}); // "some string" lifetime ends here
    std::cout << "before scope end" << std::endl;
}

As to whether it is good practice to to return an rvalue reference I believe these two answers already go into detail on the subject:

Should I return an rvalue reference parameter by rvalue reference?

Is returning by rvalue reference more efficient?

Community
  • 1
  • 1
Fibbs
  • 1,350
  • 1
  • 13
  • 23