Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can a std::reference_wrapper to a rvalue lambda work?

Tags:

c++

c++11

lambda

In this article it says the following code is valid C++11 and works with GNU's libstdc++:

int n;
std::vector<int> v;
...
std::function<bool(int)> f(std::cref([n](int i) {return i%n == 0));
std::count_if(v.begin(), v.end(), f);

The thing is that I aways believed the lambda object to be created at call site, what would make it a temporary object in this snippet, since it is not being stored on any variable, but instead a const reference to it is being created and passed to the std::function. If that is so, the lambda object should have been destroyed right alway, leaving a dangling reference inside f, that would lead to undefined behavior when used by std::count_if.

Assuming the article is not wrong, what is wrong about my mental model? When the lambda object is destructed?

like image 743
lvella Avatar asked Nov 30 '13 21:11

lvella


2 Answers

OK, let's start with the basics: the above code is certainly not legal because it is ill-formed in some rather basic ways. The line

std::function<bool(int)> f(std::cref([n](int i) {return i%n == 0));

would at bare minimum need to be written as

std::function<bool(int)> f(std::cref([n](int i) {return i%n == 0;}));

Note that the code was written in the Dr.Dobb's article as it was in the question, i.e., any statement of the code being legal is already quite questionable.

Once the simple syntax errors are resolved the next question is whether std::cref() can actually be used to bind to an rvalue. The lambda exrpession is clearly a temporary according to 5.1.2 [expr.prim.lambda] paragraph 2 (thanks to DyP for the reference). Since it would generally be a rather bad idea to bind a reference to a temporary and is prohibited elsewhere, std::cref() would be a way to circumvent this restriction. It turns out that according to 20.10 [function.objects] paragraph 2 std::cref() is declared as

template <class T> reference_wrapper<const T> cref(const T&) noexcept;
template <class T> void cref(const T&&) = delete;
template <class T> reference_wrapper<const T> cref(reference_wrapper<T>) noexcept;

That is, the statement is incorrect even after correcting the syntax errors. Neither gcc nor clang compile this code (I have used fairly recent versions of both compilers with their respective standard C++ libraries). That is, based on the above declaration this code is clearly illegal!

Finally, there is nothing which would extend the life-time of the temporary in the above expression. The only reason the life-time of a temporary is extended is when it or one of its data members is immediately bound to a [const] reference. Wrapping a function call around the temporary inhibits this life-time extensions.

In summary: the code quoted in the article is not legal on many different levels!

like image 171
Dietmar Kühl Avatar answered Nov 13 '22 08:11

Dietmar Kühl


I'm the author of the aforementioned article and I apologise for my mistake. No one else is to blame in this case. I've just ask the editor to add an errata:

1) Replace

std::count_if(v.begin(), v.end(), std::cref(is_multiple_of(n)));

with

is_multiple_of f(n);
std::count_if(v.begin(), v.end(), std::cref(f));

2) Replace

std::count_if(v.begin(), v.end(), std::cref([n](int i){return i%n == 0;}));

with

auto f([n](int i){return i%n == 0;});
std::count_if(v.begin(), v.end(), std::cref(f));

3) Replace

std::function<bool(int)> f(std::cref([n](int i) {return i%n == 0));

with

auto f1([n](int i){return i%n == 0;});
std::function<bool(int)> f(std::cref(f1));

In all cases the problem is the same (as Dietmar Kühl has nicely explained, +1 to him): we are calling std::cref on a temporary. This function returns a std::reference_wrapper storing a pointer to the temporary and this pointer will dangle if the std::reference_wrapper outlives the temporary. Basically this is what happens in case 3 above (which also contains a typo).

In cases 1 and 2, the std::reference_wrapper would not outlive the temporary. However, since the overloads of std::cref accepting temporaries (rvalues) are deleted the code should not compile (including case 3). At the time of publication, the implementations were not up to date with the Standard as they are today. The code used to compile but it doesn't when used with newer implementations of the standard library. This is not an excuse for my mistake though.

In any case, I believe the main point of the article, that is, the use of std::reference_wrapper, std::cref and std::ref to avoid expensive copies and dynamic allocations is still valid provided, of course, that the lifetime of the referred object is long enough.

Again, I apologise for the inconvenience.

Update: The article has been fixed. Thanks uk4321, DyP and, especially, lvella and Dietmar Kühl for raising and discussing the issue.

like image 41
Cassio Neri Avatar answered Nov 13 '22 08:11

Cassio Neri