按类型实例化C++ lambda

Instantiating C++ lambda by its type

本文关键字:lambda C++ 实例化 类型      更新时间:2023-10-16

我想要一种从函数制作函子的方法。现在我尝试通过 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>()

代码没有意义。想象一下,你有一个像这样的捕获 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 没有默认构造函数。 曾。 他们有时授予访问权限的唯一构造函数是(取决于他们捕获的内容)复制和/或移动构造函数。

如果创建没有公共默认构造函数的函子,则会收到相同的错误。

在 C++17 中,您可以使用 lambda constexproperator+衰减到函数指针来解决这个问题。 携带函数指针并调用函数指针的类型很容易使用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实际上是空类型可能是个好主意。 你知道,为了质量。

C++14 功能的使用可以用手动decltype和喷出typename::type关键字代替。 这个答案最初是为一个C++14的问题而写的,这个问题被关闭为这个问题的答案的副本。

活生生的例子。

我们可以假设 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 始终为空。这种情况应该始终如此(因为它需要衰减到函数指针),但标准没有明确保证这一点。所以我们使用断言至少来捕捉 lambda 的疯狂实现。然后我们只是将包装类强制转换为 lambda,因为它们都是空的。

最后,lambda 可以像这样构造:

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

No.

但是我相信 lambda 可以复制,因此您的functor_caller可以采用参数来初始化其属性。

不过,与其重新发明轮子,我会用std::function来代替。

在 C++20 之前,闭包类型不是默认可构造的,也没有默认构造函数。

由于 C++20 个无捕获的 lambda 是默认可构造的,即它们具有默认构造函数。