将 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();
}