Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I extend the lifetime of a temporary in a ranged for expression?

I'm getting dangling references while using a ranged-for loop. Consider the following C++14 expression (full example program below):

    for(auto& wheel: Bike().wheels_reference())
        wheel.inflate();

It's output is:

 Wheel()
 Wheel()
 Bike()
~Bike() with 0 inflated wheels.
~Wheel()
~Wheel()
 Wheel::inflate()
 Wheel::inflate()

Obviously something is going very wrong. Wheels are accessed beyond their lifetime and the result is 0, not the expected 2.

An easy fix is to introduce a variable for Bike in main. However, I do not control the code in main or Wheel. I can only change the struct Bike.

Is there any way to fix this example by only changing Bike?

A successful solution solution will either fail at compile time, or count 2 inflated tires and not touch any objects beyond their lifetimes.

Appendix: compile ready source

#include <cstdlib>
#include <iostream>
#include <array>
#include <algorithm>
using std::cout;
using std::endl;

struct Wheel
{
    Wheel() { cout << " Wheel()" << endl; }
    ~Wheel() { cout << "~Wheel()" << endl; }
    void inflate() { inflated = true; cout << " Wheel::inflate()" << endl; }
    bool inflated = false;
};

struct Bike
{
    Bike() { cout << " Bike()" << endl; }
    ~Bike() {
        cout << "~Bike() with " << std::count_if(wheels.begin(),    wheels.end(),
            [](auto& w) { return w.inflated; }) << " inflated wheels." << endl;
    }
    std::array<Wheel, 2>& wheels_reference() { return wheels; }
    std::array<Wheel, 2> wheels{Wheel(), Wheel()};
};

int main()
{
    for(auto& wheel: Bike().wheels_reference())
        wheel.inflate();
    return EXIT_SUCCESS;
}
like image 811
Remco Avatar asked Jan 14 '16 11:01

Remco


People also ask

How can you extend the lifetime of an object?

The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.

What does auto do in a for loop?

Range-based for loop in C++ Often the auto keyword is used to automatically identify the type of elements in range-expression. range-expression − any expression used to represent a sequence of elements.

What is a lifetime in c++?

C/C++ use lexical scoping. The lifetime of a variable or object is the time period in which the variable/object has valid memory. Lifetime is also called "allocation method" or "storage duration."

What is a ranged for loop?

Range-based for loop in C++ It executes a for loop over a range. Used as a more readable equivalent to the traditional for loop operating over a range of values, such as all elements in a container.


2 Answers

Delete the rvalue overload of wheels_reference.

std::array<Wheel, 2>& wheels_reference() & { return wheels; }
std::array<Wheel, 2>& wheels_reference() && = delete;

That way you won't return a reference to a member of a temporary.

Your example usage of the Bike class

for(auto& wheel: Bike().wheels_reference())
    wheel.inflate();

Will then refuse to compile with (clang 3.4 output):

test.cpp:31:29: error: call to deleted member function 'wheels_reference'
    for(auto& wheel: Bike().wheels_reference())
                     ~~~~~~~^~~~~~~~~~~~~~~~
test.cpp:24:27: note: candidate function has been explicitly deleted
    std::array<Wheel, 2>& wheels_reference() && = delete;
                          ^
test.cpp:23:27: note: candidate function not viable: no known conversion from 'Bike' to 'Bike' for object argument
    std::array<Wheel, 2>& wheels_reference() & { return wheels; }

If the lifetime of the temporary is manually extended things work.

Bike&& bike = Bike();
for(auto& wheel: bike.wheels_reference())
    wheel.inflate();
like image 90
jepio Avatar answered Sep 28 '22 00:09

jepio


The best solution is to stop getting members of a type through a member function call on temporary.

If wheels_reference were a non-member function, you could simply declare it like this:

wheels_reference(Bike &bike);

Since a non-const lvalue parameter cannot be attached to a temporary, you would be unable to call wheels_reference(Bike()). Since wheels_reference is a member function, you'll just have to use the member function syntax for saying the same thing:

std::array<Wheel, 2>& wheels_reference() & //<--
{ return wheels; }

If the user now tries to call Bike().wheels_reference(), the compiler will complain.

like image 25
Nicol Bolas Avatar answered Sep 28 '22 00:09

Nicol Bolas