2

Related to this question: Return value or rvalue reference? - I found that the following example does appear to be unsafe, at least with g++ 6.1.0 and Boost 1.60.0.

#include <boost/optional.hpp>

struct A {
    A();
    A(const A&);
    A(A&&);
    ~A();

    int* begin();
    int* end();

    int* buf;
};

boost::optional<A> f();

int test() {
    int res = 0;
    for (int n : f().value())
        res += n;
    return res;
}

When I look in the generated assembly code, I definitely see A::~A() being called before A::begin(), A::end(), etc.

The question is: What would be the least intrusive way to force a move construction before the temporary returned by f() goes away?

Community
  • 1
  • 1
Daniel Schepler
  • 3,043
  • 14
  • 20
  • I think `A a = f().value();` should be safe as http://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary says "a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function call". – Daniel Schepler May 13 '16 at 00:46

2 Answers2

0

I've written a simple template function that on an rvalue, calls a move constructor, but on an lvalue just returns the same thing.

template <typename T>
T extend_if_rval(T&& x) {
    return std::forward<T>(x);
}

Then, if I change the for to:

for (int n : extend_if_rval(f().value()))

the generated assembly looks good: it first calls A::A(A&&) before A::begin(), A::end(), and eventually A::~A() after the end of the loop. (Of course it can still throw an exception, but that's by the design of the example.)

Daniel Schepler
  • 3,043
  • 14
  • 20
  • 2
    `template T extend_if_rval(T&& x) { return std::forward(x); }`. No need for the second overload. Maybe `static_assert(!is_rvalue_reference_v)` if you want to guard against idiots. – T.C. May 13 '16 at 02:45
  • Thanks, I've tested it and I'll update the answer accordingly. – Daniel Schepler May 13 '16 at 17:17
0

Create an explicit temporary:

for (int n : A(f().value())) { /* ... */ }
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Sure, that works if `A` is a simple type - but what if it were something more complex that you didn't want to type? I guess that was sort of an implicit point of my template function, to deduce the type to construct rather than having to name it. (Also, I think the `std::move` here is unnecessary.) – Daniel Schepler May 13 '16 at 00:40