如何在声明和定义中拆分静态lambda?

时间:2019-02-27 12:02:09

标签: c++ lambda static c++17 header-files

我在类中有一个静态lambda,它在标头文件中声明和定义为:

class A final {
    // inline could be removed of course for splitting
    static inline auto foo = [](auto& param1, auto& param2 auto& param3) {
        // do stuff
        return;
    }
}
// compiles fine

使用诸如static int x = 42这样的静态变量,我可以像这样拆分声明和定义:

// bar.h:
class Bar {
    static int x;
}
// bar.cpp:
#include "bar.h"
int Bar::x = 42;

如何用上述lambda归档同一件事?当然,更改签名是可以的。

3 个答案:

答案 0 :(得分:3)

基本问题是每个lambda表达式都有其自己的单独类型(see also this answer)。由于您需要知道声明变量的类型,并且需要知道lambda表达式才能知道其类型,因此如果不知道lambda表达式本身,就无法声明变量来保存lambda表达式的结果。

只要两个地方都知道lambda表达式,就可以声明一个变量来保存lambda并分别定义该变量。例如:

inline auto makeFoo() {
    return [](auto& param1, auto& param2, auto& param3) {
        // do stuff
        return;
    };
}

class A final {
    static decltype(makeFoo()) foo;
};

decltype(makeFoo()) A::foo = makeFoo();

但是,不可能将变量的声明与lambda表达式分开(即,不能仅在放置变量定义的文件中使用lambda表达式)。

不捕获任何内容的lambda表达式可以转换为指向函数的指针。如果您不需要lambda来捕获任何内容(例如您的示例中的内容),并且只需要一个特定签名的可调用对象,则只需声明A::foo为函数指针类型并初始化{{1 }}和匹配的lambda:

A::foo

如果您确实需要示例所建议的通用lambda(带有class A final { static void (*foo)(int&, float&, double&); }; void (*A::foo)(int&, float&, double&) = [](auto& param1, auto& param2, auto& param3) { // do stuff return; }; 参数的lambda),那么它也不起作用,很可能您不走运。使用通用调用运算符意味着您的闭包类型的auto函数必须是函数模板。拥有operator ()函数模板意味着任何人都必须知道其定义才能实际进行调用。即使您编写自己的类而不使用lambda表达式,也无法让任何人在不知道其定义的情况下调用泛型operator ()。您所要做的就是为需要支持的所有签名声明operator ()模板的显式实例化,并分别定义它们。但这又需要您实际上预先知道需要哪些可调用的具体签名才能支持...

答案 1 :(得分:2)

  

如何用上述lambda归档同一件事?

不能。您必须始终声明变量的类型。如果在变量声明后定义lambda,则无法从初始化程序中推断出声明的类型。但是您不能在定义lambda之前引用它的类型,因为该类型是匿名的。

您可以简单地使用命名类型的函数对象来代替lambda(即匿名函数对象)。然后,您可以拆分函数定义。另外,您必须声明函数的返回类型,因为没有函数的定义就无法推断出它的类型:

class A final {
    constexpr struct Foo {
        template<class Param1, class Param2, class Param3>
        void operator()(Param1&, Param2&, Param3&) const;
    } foo{};
};

但是,您可能会注意到,该函数实际上是一个函数模板。这是因为您在lambda中使用了auto参数。如果希望在标头之外定义模板,则必须将实例化限制为一个有限的集合,并显式实例化那些定义了模板的实例:

// the definition
template<class Param1, class Param2, class Param3>
void A::Foo::operator()(Param1&, Param2&, Param3&) const {
    // do stuff
    return;
}

// explicit instantiations
template void A::Foo::operator()<int, int, int>(int&, int&, int&) const;
template void A::Foo::operator()<int, double, float>(int&, double&, float&) const;

如果您尝试使用未实例化的参数进行调用,则在未定义模板的翻译单元中,将出现链接器错误。

如果您希望不限制参数,那么您的要求就有冲突。不受约束的模板只能通过在标头中定义模板来实现。


另一方面,您可能首先要考虑是否需要一个功能对象。您尚未证明您需要它。如果您将其设为静态成员函数(模板)而不是函数对象,则上述示例同样适用。

答案 2 :(得分:1)

您仍然可以使用旧方法创建函子:

struct Foo
{
    template <typename T1, typename T2, typename T3>
    void operator ()(T1& param1, T2& param2, T3& param3) const;
};

template <typename T1, typename T2, typename T3>
void Foo::operator ()(T1& param1, T2& param2, T3& param3) const
{
    /*..*/
}

class A final {
    // inline could be removed of course for splitting
    static const Foo foo;
};

// in .cpp
const Foo A::foo{};