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

How to split static lambda in declaration and definition?

本文关键字:拆分 静态 lambda 定义 声明      更新时间:2023-10-16

我在类中有一个静态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进行同一件事?当然可以更改签名。

基本问题是每个lambda表达式都有其独立的类型(另请参见此答案)。由于您需要知道要声明变量的类型,并且您需要知道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声明为功能指针类型,并使用匹配的lambda来初始化A::foo的定义:

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 ()函数必须是功能模板。具有operator ()功能模板意味着必须知道其定义才能真正拨打电话。即使您写了自己的课程而不是使用lambda表达式,也无法让任何人在不知道其定义的情况下调用通用operator ()。您所能做的就是为您需要支持的所有签名和分别定义这些签名的operator ()模板的显式实例。但这再次要求您实际上知道您需要可调用的哪些具体签名来支持…

如何用上面的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;

如果您尝试使用未实例化的参数,则在未定义模板的翻译单元中,则会有一个链接器错误。

如果您想保持参数不受限制,那么您的要求矛盾。仅通过定义标题中的模板才能实现不受约束的模板。


另一方面,您可能需要考虑是否首先需要功能对象。您没有证明自己的需求。上面的示例同样可以使其成为静态成员函数(模板)而不是函数对象。

您仍然可以以旧方式创建函子:

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{};