C ++将lambda函数保存为成员变量,而没有用于优化的函数指针

时间:2018-08-14 09:06:20

标签: c++ optimization lambda inline

我想用C ++编写一个将lambda函数保存为成员变量的类。尽可能高效地做到这一点将是很棒的。例如,我读了这个线程Why can lambdas be better optimized by the compiler than plain functions?,因此我想避免使用函数指针。

到目前为止,我最好的解决方法是:

template<typename F>
class LambdaClass {
  private:
    F lambdaFunc;
  public:
    LambdaClass(F &_lambdaFunc): lambdaFunc(_lambdaFunc) {}
};

我将按以下方式使用此类:

auto lambdaFunc = [](int _a) -> int { return _a; };
LambdaClass<decltype(lambdaFunc)> lambdaClassObject<decltype(lambdaFunc)>(lambdaFunc);

在我看来,使用它看起来并不有趣。因此,我感兴趣的是,首先,从编译器可以内联保存的成员lambda函数的调用的角度来看,这段代码是否有效,其次,如何使这段代码更漂亮?

编辑:我正在使用C ++ 11。

4 个答案:

答案 0 :(得分:8)

在您的示例中

LambdaClass<decltype(lambdaFunc)> lambdaClassObject<decltype(lambdaFunc)>(lambdaFunc);

第二个模板参数列表的语法不正确。这应该只是

LambdaClass<decltype(lambdaFunc)> lambdaClassObject(lambdaFunc);
  

所以我首先对这段代码是否有效表示感兴趣,因为编译器可以内联保存的成员lambda函数的调用

是的,可以像允许直接使用lambda一样以允许优化的方式使用此类。 template参数是lambda表达式的确切类型,并且模板替换发生在编译时,通常产生的结果就像您通过不使用模板编写代码所得到的一样。

  

如何写出更漂亮的代码?

@lubgr的答案已经提到了C ++ 17的“类模板推导”和“推导指南”功能。在C ++ 17之前,避免指定类模板参数的通常技巧是使用助手“ make function”:

template <typename F>
auto makeLambdaClass(F&& func) ->
    LambdaClass<typename std::decay<F>::type>
{ return { std::forward<F>(func); } }

现在您可以做

auto lambdaFunc = [](int _a) -> int { return _a; };
auto lambdaClassObject = makeLambdaClass(lambdaFunc);

但要进一步走下去,使之

auto lambdaClassObject = makeLambdaClass( [](int _a) -> int { return _a; } );

也可以,您还需要确保该类具有一个接受右值的构造函数,而不仅仅是一个非常量左值:

template<typename F>
class LambdaClass {
  private:
    F lambdaFunc;
  public:
    LambdaClass(const F &lambdaFunc_): lambdaFunc(lambdaFunc_) {}
    LambdaClass(F &&lambdaFunc_) : lambdaFunc(std::move(lambdaFunc_)) {}
};

顺便说一句,该类将与不是lambda的闭包类型的可调用类同样有效,因为lambda只是使用operator()定义类的更方便的方式:

class UniqueUIntGenerator
{
public:
    unsigned int operator()() const noexcept
    { return num++; }
private:
    static unsigned int num;
};
unsigned int UniqueIntGenerator::num = 0;

LambdaClass<UniqueIntGenerator> gen{UniqueIntGenerator{}};

答案 1 :(得分:4)

当您可以使用C ++ 17时,可以对类使用模板参数推导。然后实例化如下:

auto lambdaFunc = [](int _a) -> int { return _a; };
LambdaClass lambdaClassObject(lambdaFunc);

看起来更有趣。内联lamdba调用时,该用例没有施加任何限制。

请注意,可能需要将临时变量传递给构造函数。在这种情况下,请使用一个右值引用,该引用将由必要的推导指南显式转换为转发引用:

template<typename F> class LambdaClass {
    private:
        F lambdaFunc;
    public:
        LambdaClass(F&& _lambdaFunc) : lambdaFunc(std::forward<F>(_lambdaFunc)) {}
};

// Deduction guide, makes the ctor accept lvalue and rvalue arguments:
template<class F> LambdaClass(F&&) -> LambdaClass<F>;

您现在可以使用上面或下面的lvalue-lamdba实例化LambdaClass对象

LambdaClass lambdaClassObject([](){ /* Do stuff. */ });

正如@ Holt,@ aschepler和@Caleth在评论中指出的那样,当将左值传递给构造函数时,类型推导会导致F&,这不太可能是所需的实例化。我无法按照{Caleth的建议,在std::remove_reference_tstd::decay_t中进行推导指南中的工作,但是找到了一个需要 no 推导指南的解决方案。重载的构造函数:

template<typename F> class LambdaClass {
    private:
        F lambdaFunc;
    public:
        LambdaClass(F&& _lambdaFunc) : lambdaFunc(std::forward<F>(_lambdaFunc)) {}
        LambdaClass(F& _lambdaFunc) : lambdaFunc(_lambdaFunc) {}
};

// Note: Deduction guide is gone. The first ctor accepts only an rvalue reference.

这允许使用编译器推导的具有“直观”类型的左值或右值参数进行构造。

答案 2 :(得分:3)

在C ++ 11中,只有函数可以自动推导模板参数。因此,只需编写一个make函数:

template<typename F>
LambdaClass<F> makeLambdaClass(F&& f)
{
  return LambdaClass<F>(std::forward<F>(f));
}

这可以让您在用法中省略template参数:

auto lambdaFunc = [](int _a) -> int { return _a; };
auto lambdaClassObject = makeLambdaClass(lambdaFunc);

但是,您应该意识到,这只会使问题向上蔓延:如果其他人想使用LambdaClass作为成员,则他们要么做相同的类来模仿恶作剧,要么进行自己的类型擦除。也许这对您来说不是问题,但您应该意识到这一点。

答案 3 :(得分:2)

在C ++ 11中,最简单的方法是将类型推断委托给模板函数:

template<class F>
auto make_lambda_class(F&& f) -> LambdaClass<decltype(f)>
{ return f; }

您会这样称呼它:

auto lambdaFunc = [](int _a) -> int { return _a; };
auto lambdaClassObject = make_lambda_class(lambdaFunc);

但是,令人遗憾的是,您无法将右值传递给make_lambda_class。您需要使用C ++ 17才能做到这一点,例如lubgr shown