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?
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!
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With