Reputation: 850
Returning a certain lambda wrapped as an std::function
produces wrong results:
#include <functional>
#include <iostream>
#include <tuple>
template <typename T>
std::function<const T&()> constant(const T& c) {
return [c]() noexcept -> const T&{ return c; };
}
template <typename T>
std::function<std::tuple<T>()> zip(const std::function<T()>& f) {
return [f]() { return std::tuple{f()}; };
}
int main() {
const auto good = [f = constant(1.0)]() { return std::tuple{f()}; };
const auto bad = zip(constant(1.0));
std::cout << "good: " << std::get<0>(good()) << std::endl;
std::cout << "bad: " << std::get<0>(bad()) << std::endl;
}
Output:
$ ./a.out
good: 1
bad: 6.95282e-310
It looks like undefined behaviour. What is going on?
Upvotes: 2
Views: 109
Reputation: 850
The reason is the following: the tuple created inside zip is automatically deduced to be a std::tuple<double>
, but zip returns a std::function<std::tuple<const double&>()>
. The returned tuple contains a reference to a temporary, which is undefined behaviour.
The fix is simply explicitly adding the type of the tuple element:
template <typename T>
std::function<std::tuple<T>()> zip(const std::function<T()>& f) {
return [f]() { return std::tuple<T>{f()}; };
//was return [f]() { return std::tuple{f()}; };
}
Boiling down the issue in my source code to this was very nasty. For one, debugging these lambdas packed into std::function
s is not fun. I tried g++'s -fsanitize=undefined
, but this was not caught, although it catches the missing return type in this function:
template <typename T>
std::function<const T&()> constant_bad(const T& c) {
return [c]() noexcept { return c; };
}
I filed a bug in GCC.
I would appreciate other comments and discussion about this issue! For example, would it be good, if possible, that compilers warn about this type of implicit conversion? Eg when implicitly converting a lambda returning T
into a std::function<const T&()>
.
Upvotes: 1
Reputation: 12849
#include <functional>
#include <type_traits>
template <typename T>
std::function<const T& ()> constant(const T& c)
{
return [c]() noexcept -> const T&
{
// the captured variable is no longer a reference type!
static_assert(std::is_same_v<decltype(c), std::remove_reference_t<const T&>>);
return c;
};
}
int main()
{
auto fn = constant(42);
auto answer = fn();
}
Upvotes: 1