【问题标题】:Instantiating C++ lambda by its type按类型实例化 C++ lambda
【发布时间】:2012-06-17 16:11: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>()

【问题讨论】:

  • 我什至不能在main 中声明functor_type 类型的变量。需要先让它发挥作用。
  • @user315052: 这其实是同一个问题。
  • 你试过F f(t); 吗?目前,functor_caller 不知道t。
  • 没有。我不想用它。我尝试获取 lambda 类型,而不是默认构造它。
  • @Andigor:这没有意义。 lambda 类型没有默认值。编译器会告诉你很多(没有默认 ctor)。

标签: c++ lambda c++11 instantiation


【解决方案1】:

代码没有意义。想象一下,你有一个像这样的捕获 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:如果你使用模板风格,那么你甚至不需要构造一个中间函子——你可以直接传递函数(指针)......并注意n不一定在范围内,也不是类型的有意义的一部分。
【解决方案2】:

Lambda 没有默认构造函数。曾经。他们有时可以访问的唯一构造函数是(取决于它们捕获的内容)复制和/或移动构造函数。

如果你创建一个没有公共默认构造函数的函子,你会得到同样的错误。

在 C++17 中,您可以使用 constexpr lambda 和 operator+ 来衰减函数指针来解决这个问题。带有函数指针并调用它的类型很容易使用auto 模板参数。

在 C++11 中你得有点 hacky。

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 问题编写的,该问题已作为此问题的副本关闭。

live example.

【讨论】:

  • 恐怕我无法让它完全正常工作。你能发布一个完整的代码示例吗?
【解决方案3】:

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

最后可以像这样构造 lambda:

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

【讨论】:

  • 不错的解决方案。只需稍作修改:*reinterpret_cast&lt;F*&gt;(nullptr)(...) 将在运行时导致访问冲突,只要F::operator() 尝试取消引用自身而不是在wrapper 对象之后静默破坏内存。 static_assert() 向我们保证 'F' 没有字段,但是......你永远不知道 F 做了什么。
  • 取消引用空指针不保证访问冲突。
【解决方案4】:

没有。

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

不过,我不会重新发明轮子,而是使用std::function。

【讨论】:

  • 没有。在我的问题上下文中使用 std::function 的示例。
  • @Andigor:我终于明白了这个问题。 lambda 的 body 不是其类型的一部分。因此,即使有可能,默认从其类型构造一个 lambda 也是没有意义的。这就像默认构造void (*)(),你得到的只是一个指向假设函数的空指针,该函数不接受参数并且不返回任何内容=> 这个空指针没有任何意义!这就是为什么您需要复制现有的 lambda。
  • 谢谢!我认为编译器在看到 lambda 声明时会生成简单的函子,并且 lambda 的主体处于重载operator() 似乎我错了......
  • @Andigor:据我所知,这只是标准描述它如何与语言的现有功能交互的一种方式。
  • std::function 与原始 lambda 调用相比非常慢
猜你喜欢
  • 2018-12-19
  • 2019-10-17
  • 2023-03-19
  • 1970-01-01
  • 1970-01-01
  • 2011-01-01
  • 1970-01-01
  • 2013-10-03
相关资源
最近更新 更多