将 lambda 包装为 std::function 会产生错误的结果(是模板参数推导的危险)
Wrapping lambda as std::function produces wrong results (was The dangers of template argument deduction)
返回包装为 std::function
的某个 lambda 会产生错误的结果:
#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;
}
输出:
$ ./a.out
good: 1
bad: 6.95282e-310
看起来像是未定义的行为。这是怎么回事?
原因如下:在 zip 中创建的元组自动推导为 std::tuple<double>
,但 zip return 是 std::function<std::tuple<const double&>()>
。 returned 元组包含对临时对象的引用,这是未定义的行为。
修复只是显式添加元组元素的类型:
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()}; };
}
咆哮...
将我的源代码中的问题归结为这个非常讨厌。一方面,调试这些打包到 std::function
中的 lambda 并不好玩。我尝试了 g++ 的 -fsanitize=undefined
,但这没有被捕获,尽管它捕获了此函数中缺少的 return 类型:
template <typename T>
std::function<const T&()> constant_bad(const T& c) {
return [c]() noexcept { return c; };
}
我在 GCC 中提交了 bug。
非常感谢有关此问题的其他评论和讨论!例如,如果可能的话,编译器警告这种类型的隐式转换是否好?例如,当将 lambda returning T
隐式转换为 std::function<const T&()>
.
时
#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();
}
返回包装为 std::function
的某个 lambda 会产生错误的结果:
#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;
}
输出:
$ ./a.out
good: 1
bad: 6.95282e-310
看起来像是未定义的行为。这是怎么回事?
原因如下:在 zip 中创建的元组自动推导为 std::tuple<double>
,但 zip return 是 std::function<std::tuple<const double&>()>
。 returned 元组包含对临时对象的引用,这是未定义的行为。
修复只是显式添加元组元素的类型:
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()}; };
}
咆哮...
将我的源代码中的问题归结为这个非常讨厌。一方面,调试这些打包到 std::function
中的 lambda 并不好玩。我尝试了 g++ 的 -fsanitize=undefined
,但这没有被捕获,尽管它捕获了此函数中缺少的 return 类型:
template <typename T>
std::function<const T&()> constant_bad(const T& c) {
return [c]() noexcept { return c; };
}
我在 GCC 中提交了 bug。
非常感谢有关此问题的其他评论和讨论!例如,如果可能的话,编译器警告这种类型的隐式转换是否好?例如,当将 lambda returning T
隐式转换为 std::function<const T&()>
.
#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();
}