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

问题描述 投票:0回答:3

我在一个类中有一个静态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来达到同样的目的?当然,改变签名是可以的。

c++ lambda static c++17 header-files
3个回答
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是函数指针类型并用匹配的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 ()模板的显式实例化,并单独定义它们。但是,再一次,要求你事先确实知道你需要哪些具体签名才能支持你的...


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;

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

如果你希望保持参数不受约束,那么你就会有相互冲突的要求。只能通过在标题中定义模板来实现无约束的模板。


另一方面,您可能想要首先考虑是否需要函数对象。你没有证明你需要它。如果您要将其设置为静态成员函数(模板)而不是函数对象,则上述示例也可以正常工作。


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{};
© www.soinside.com 2019 - 2024. All rights reserved.