通过其类型实例化C++ lambda函数

21

我希望有一种方法可以从函数中创建函数对象。现在,我尝试通过 lambda 函数来封装函数调用,并稍后实例化它。但编译器说 lambda 构造函数被删除了。 那么,有没有办法编译这段代码?或者还有其他方法吗?

#include <iostream>  

void func()
{
    std::cout << "Hello";
}

auto t = []{ func(); };
typedef decltype(t) functor_type;

template <class F>
void functor_caller()
{
    F f;
    f();
}

int main()
{
    functor_caller<functor_type>();
    return 0;
}

现在我遇到编译器错误,错误信息如下:
error: use of deleted function  '<lambda()>::<lambda>()'

error: a lambda closure type has a deleted default constructor

我认为唯一的方法就是使用宏:

#define WRAP_FUNC(f) \
struct f##_functor       \
{                       \
    template <class... Args >                             \
    auto operator()(Args ... args) ->decltype(f(args...)) \
    {                                                     \
        return f(args...);                                \
    }                                                     \
};

然后。
WRAP_FUNC(func);

然后(在主函数中)

functor_caller<func_functor>()

我甚至无法在main函数中声明一个functor_type类型的变量。需要先解决这个问题。 - jxh
1
@user315052:那其实是同样的问题。 - Nawaz
你试过 F f(t); 吗?目前,functor_caller 不知道 t - MSalters
不,我不想使用它。我尝试获取lambda的类型,然后默认构造它。 - andigor
@Andigor:这没有意义。Lambda类型没有默认值。编译器会告诉你(没有默认构造函数)。 - MSalters
谢谢。我也阅读了编译器的输出。问题的主要关键词是为什么和如何。而且我已经得到了Matthieu M.的答案。 - andigor
5个回答

8

lambda表达式没有默认构造函数,永远不会有。它们唯一可能提供的构造函数是(取决于它们捕获了什么)复制和/或移动构造函数。

如果您创建一个没有公共默认构造函数的函数对象,您将收到相同的错误。

在C++17中,您可以使用constexpr lambda和operator+来衰减为函数指针。使用auto模板参数很容易得到一个携带函数指针并调用它的类型。

在C++11中,您需要进行一些技巧性的操作。

template<class F>
struct stateless_lambda_t {
  static std::aligned_storage_t< sizeof(F), alignof(F) >& data() {
    static std::aligned_storage_t< sizeof(F), alignof(F) > retval;
    return retval;
  };
  template<class Fin,
    std::enable_if_t< !std::is_same< std::decay_t<Fin>, stateless_lambda_t >{}, int> =0
  >
  stateless_lambda_t( Fin&& f ) {
    new ((void*)&data()) F( std::forward<Fin>(f) );
  }
  stateless_lambda_t(stateless_lambda_t const&)=default;
  template<class...Args>
  decltype(auto) operator()(Args&&...args)const {
    return (*static_cast<F*>( (void*)&data() ))(std::forward<Args>(args)...);
  }
  stateless_lambda_t() = default;
};
template<class F>
stateless_lambda_t<std::decay_t<F>> make_stateless( F&& fin ) {
  return {std::forward<F>(fin)};
}

现在我们可以:

auto t = make_stateless([]{ func(); });

你的代码工作了。

使用 static_assert 或 SFINAE 来确保 F 实际上是一个空类型可能是个好主意。为了质量,请注意。

可以用手动的 decltype 和喷出 typename::type 关键字来代替使用 C++14 特性。这个答案最初是为一个被关闭为此问题的重复问题而撰写的。

实时例子


很抱歉,我无法使其正常工作。您能否发布完整的代码示例? - Knarf

8

这段代码没有意义。想象一下你有一个捕获lambda表达式,像这样:

{
    int n = 0;
    auto t = [&n](int a) -> int { return n += a; };
}

如何默认构造一个类型为decltype(t)的对象?

正如@Matthieu所建议的,您可以将lambda表达式封装到function对象中:

std::function<int(int)> F = t;

或者您可以根据lambda(或任何可调用实体)的类型直接在调用站点上创建模板:

template <typename F>
int compute(int a, int b, F f)
{
    return a * f(b);  // example
}

用法: int a = 0; for (int i : { 1, 3, 5 }) { a += compute(10, i, t); }

如果可能的话,第二种风格更可取,因为转换为std::function是一个非平凡的、潜在昂贵的操作,就像通过结果对象进行实际函数调用一样。然而,如果您需要存储一个异构可调用实体的统一集合,则std::function可能是最简单和最方便的解决方案。


在你的例子中,理论上,lambda 的默认构造意味着我得到对 n 的引用,如果该对象已被销毁,则可能会出现悬垂引用。但是如果 n 存在,则我得到了正确的引用。 - andigor
@Andigor:如果你使用模板风格,那么你甚至不需要构建一个中间的函数对象 - 你可以直接传递函数指针...注意n不一定在作用域内,也不是类型的有意义部分。 - Kerrek SB

7
我们可以假设lambda表达式始终为空,因此,我们只需要从另一个空类型进行转换,因为它们具有相同的内存布局。因此,我们构建一个包装器类,使其成为可默认构造函数对象:
template<class F>
struct wrapper
{
    static_assert(std::is_empty<F>(), "Lambdas must be empty");
    template<class... Ts>
    auto operator()(Ts&&... xs) const -> decltype(reinterpret_cast<const F&>(*this)(std::forward<Ts>(xs)...))
    {
        return reinterpret_cast<const F&>(*this)(std::forward<Ts>(xs)...);
    }
};

现在添加了一个静态断言,以确保lambda始终为空。这应该总是如此(因为需要衰减为函数指针),但标准没有明确保证。因此,我们使用assert来至少捕获疯狂的lambda实现。然后,我们只需将包装类强制转换为lambda,因为它们都为空。
最后,可以像这样构造lambda:
template <class F>
void function_caller()
{
    wrapper<F> f;
    f();
}

不错的解决方案。只需要稍作修改:*reinterpret_cast<F*>(nullptr)(...)会在运行时导致访问冲突,每当F::operator()尝试对自身进行解引用而不是在“wrapper”对象之后默默地破坏内存。 static_assert()确保我们'F'没有字段,但是...你永远不知道F会做什么。 - Mykola Bohdiuk
使用空指针解引用时,并不能保证会出现访问冲突。 - Paul Fultz II

2

不行。

然而我相信Lambda表达式可以被复制,所以你的functor_caller可以接受一个参数来初始化它的属性。

但是,与其重新发明轮子,我会使用std::function


在我的问题背景下,使用std::function的示例。 - andigor
3
我明白了这个问题。Lambda的“主体”不是它类型的一部分。因此,即使可能从其类型进行默认构造lambda,也没有意义。这就像默认构造void (*)(),你只会得到一个指向一个假想函数的空指针,该函数不带参数且返回值为空=>你无法对此空指针做任何有意义的事情!这就是为什么需要复制现有的lambda。 - Matthieu M.
谢谢!我曾以为编译器在 lambda 声明时生成简单的函数对象,并且 lambda 的主体在重载的 operator() 中。看来我错了... - andigor
@Andigor:就我所理解的,这只是标准描述其与语言现有特性交互的一种方式。 - Matthieu M.
std::function 相对于原始的 lambda 调用来说速度慢得要死 - Jean-Bernard Jansen

1

在C++20之前,闭包类型不具备默认构造函数,因此无法进行默认构造。

自C++20起,无捕获的lambda表达式具有默认构造函数,即可以进行默认构造。


这个问题更好地总结了变化。https://dev59.com/dlMI5IYBdhLWcg3wn9H6 - ghostcore07

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接