How to extend the lifetime of a temporary in an expression for a range?

I get dangling links when using a loop for a range. Consider the following C ++ 14 expression (full program example below):

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

At the exit:

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

Obviously, something is very wrong. Wheels are accessible outside their life, and the result is 0, not expected.

An easy fix is ​​to enter a variable for Bike in main . However, I do not control the code in main or Wheel . I can change the structure of the Bike .

Is there a way to fix this example just by changing Bike ?

A successful solution will either fail at compile time, or score 2 inflated tires and not touch any objects outside of their lives.

Application: ready-made compilation source code

 #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; } 
+6
source share
4 answers

Remove congestion raleue wheels_reference .

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

This way, you will not return the link to the temporary member.

Bike Class Example

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

Then it will refuse to compile with (output of clang 3.4):

 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 a temporary user is increased manually, everything works.

 Bike&& bike = Bike(); for(auto& wheel: bike.wheels_reference()) wheel.inflate(); 
+6
source

The best solution is to stop getting type members by calling a member function at a temporary level.

If wheels_reference was a non-member function, you can simply declare it as follows:

 wheels_reference(Bike &bike); 

Since the non-const lvalue parameter cannot be bound to a temporary parameter, you cannot call wheels_reference(Bike()) . Since wheels_reference is a member function, you just need to use the syntax of the member function to express 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.

+3
source

The following terrible device seems to satisfy all conditions:

 #include <memory> struct Bike { // Bike() { cout << " FakeBike()" << endl; } // ~Bike() { cout << "~FakeBike()" << endl; } struct RealBike; struct Wrap { std::shared_ptr<RealBike> parent; auto begin() { return parent->wheels.begin(); } auto end() { return parent->wheels.end(); } }; struct RealBike { RealBike() { cout << " Bike()" << endl; } ~RealBike() { cout << "~Bike() with " << std::count_if(wheels.begin(), wheels.end(), [](auto& w) { return w.inflated; }) << " inflated wheels." << endl; } std::array<Wheel, 2> wheels; }; std::shared_ptr<RealBike> real = std::make_shared<RealBike>(); Wrap wheels_reference() { return Wrap{real}; } }; 

I don't like the fact that it requires wrapping the entire API std::array<Wheel, 2> in Wrap .

+1
source

You can simply add a couple of cv-ref-qual failures from the member function wheels_reference :

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

Note that if the object is temporary ( && case), then you must return a value (constructed from copies of the data element or even better made from it) or a link.

With all four overloads available, you can use all possible use cases.

+1
source

All Articles