Anning Wuwang
Anning Wuwang

Reputation: 343

A temporary object in range-based for-loop

In Range-based for loop on a temporary range, Barry mentioned that the following is not affected by the destroyed temporary object, and I tested member v indeed exists throughout the for-loop (as the destructor ~X didn't get called throughout the for-loop). What is the explanation?

struct X {
    std::vector<int> v;

    ~X()
    {
    }
};

X foo()
{
    return X();
}

for (auto e : foo().v) {
    // ok!
}

Upvotes: 14

Views: 1954

Answers (2)

Amir Kirsh
Amir Kirsh

Reputation: 13790

Temporary lifetime extension is achieved when a reference variable is bonded to a temporary directly, but not only. For the exact list of temporary lifetime extension, see in the specification: [class.temporary].

The answer provided by his holiness @HolyBlackCat is very good, but I feel some examples are required.

⦿ binding a temporary directly

// function prototype
std::string foo(); 

// calling foo:
const auto& b = foo(); // lifetime is extended, directly bind to a temporary

// also, similarly:
const std::string& s = "hi"; // lifetime is extended, the same

According to the language rules, temporary lifetime extension can be also achieved in any of the following cases:

⦿ parentheses ( ) (grouping, not a function call)

const auto& a = (foo()); // lifetime is extended, grouping with parenths is ok 
const std::string& s = ("hello "s + "world"); // lifetime is extended, the same

For the next cases, let's add the following struct:

struct A {
    std::string str = "hey";
    int arr[3] = {2, 3, 4};
    int* ptr = arr;
    const auto& foo() const {
        return str;
    }
};

⦿ member access ., .*

const auto& b1 = A().str; // lifetime of A() is extended
const auto& b2 = A().arr; // lifetime of A() is extended
const auto& b3 = A().ptr; // lifetime of A() is extended
// BUT -
const auto& b4 = *A().ptr; // lifetime of A() is NOT extended (b4 dangling)

// pointer to member access
const auto& str_ptr = &A::str;
const auto& arr_ptr = &A::arr;
const auto& ptr_ptr = &A::ptr;

const auto& c1 = A().*str_ptr; // lifetime of A() is extended
const auto& c2 = A().*arr_ptr; // lifetime of A() is extended
const auto& c3 = A().*ptr_ptr; // lifetime of A() is extended

// BUT - not for a member function
const auto& foo_ptr = &A::foo;
// below initialization is bounded to a function call result
// not to a member access
const auto& c4 = (A().*foo_ptr)(); // lifetime of A() is NOT extended (c4 dangling)

⦿ array access [ ] (not overloaded; must use an array and not a pointer)

const auto& d1 = A().arr[0]; // lifetime of A() is extended
// BUT - not for pointers
// pointer access with []
const auto& d2 = A().ptr[0]; // lifetime of A() is NOT extended (d2 dangling)
// neither for overloaded []
const auto& d3 = A().str[0]; // lifetime of A() is NOT extended (d3 dangling)

⦿ ternary operator ? :

const auto& e1 = true? A() : A(); // lifetime of the 1st A() is extended
const auto& e2 = false? A() : A(); // lifetime of the 2nd A() is extended

⦿ comma operator , (not overloaded)

const auto& f1 = (A(), A()); // lifetime of the 2nd A() is extended

⦿ any cast that doesn't involve a "user-defined conversion" (presumably uses no constructors nor conversion operators)

const auto& g1 = const_cast<const A&&>(A()); // lifetime of A() is extended

const double& g2 = A().arr[0]; // lifetime of A() is NOT extended
                               // but this is a valid ref to a double
                               // converted from an int, as a temporary

For a casting that doesn't extend lifetime, let's add an additional class:

class B {
    const A& a;
public:
    B(const A& a): a(a){}
};

The following casting goes through user-defined casting and thus will not extend the life time of A:

const auto& g3 = ((B&&)A()); // lifetime of A() is NOT extended (g3 dangling)

Upvotes: 1

HolyBlackCat
HolyBlackCat

Reputation: 96579

This is an obscure form of temporary lifetime extension. Normally you have to bind the temporary directly to the reference for it to work (e.g. for (auto x : foo())), but according to cppreference, this effect propagates through:

  • parentheses ( ) (grouping, not a function call),
  • array access [ ] (not overloaded; must use an array and not a pointer),
  • member access ., .*,
  • ternary operator ? :,
  • comma operator , (not overloaded),
  • any cast that doesn't involve a "user-defined conversion" (presumably uses no constructors nor conversion operators)

I.e. if a.b is bound to a reference, the lifetime of a is extended.

Upvotes: 17

Related Questions