我想用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。
答案 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_t
或std::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。