Lambda和等号/不等号运算符

3

我有一个关于lambda表达式的相等比较的问题。我尝试阅读了一些相关资料,但却没有找到关于这个的任何内容。

[] (Args ...args) -> ReturnType { ... };

对于这种没有闭包的lambda表达式,因为它们有空的捕获列表,所以 ==!= 运算符的工作方式与静态函数相同(好吧,似乎编译器也生成它们作为静态函数)。但对于带有闭包的任何比较尝试都会导致编译错误。
以下是一个简单的程序示例:
#include <typeinfo>
#include <iostream>

struct WrapperBase {
    virtual ~WrapperBase() = default;

    virtual bool operator==(WrapperBase& v) = 0;
    virtual bool operator!=(WrapperBase& v) = 0;
};

template<typename _Tp>
struct Wrapper : WrapperBase {
    Wrapper(const _Tp& v) : value(v) { }

    bool operator==(WrapperBase& v) override {
        try {
            Wrapper<_Tp>& vv = dynamic_cast<Wrapper<_Tp>&>(v);
            return value == vv.value;
        }
        catch(std::bad_cast& err) { }

        return false;
    }
    bool operator!=(WrapperBase& v) override {
        try {
            Wrapper<_Tp>& vv = dynamic_cast<Wrapper<_Tp>&>(v);
            return value != vv.value;
        }
        catch(std::bad_cast& err) { }

        return true;
    }

    //

    _Tp value;
};

template<typename _Tp>
WrapperBase* create_wrapper(const _Tp& v) {
    return new Wrapper<_Tp>(v);
}

struct Base {
    Base(int a, int b) : wrapper(nullptr), a(a), b(b) { }
    virtual ~Base() { delete wrapper; }

    virtual WrapperBase* create_wrapper() = 0;

    WrapperBase* wrapper;
    int a;
    int b;
};
struct ClassA : Base {
    ClassA(int a, int b) : Base(a, b) {
        wrapper = create_wrapper();
    }

    WrapperBase* create_wrapper() override {
        auto lambda = [] (int v1, int v2) { return v1 + v2; };
        return ::create_wrapper(lambda);
    }
};

struct ClassB : Base {
    ClassB(int a, int b) : Base(a, b) {
        wrapper = create_wrapper();
    }

    WrapperBase* create_wrapper() override {
        auto lambda = [=] (int v1, int v2) { return a + b + v1 + v2; };
        return ::create_wrapper(lambda);
    }
};

int main(int argc, char** argv) {
    std::cout << std::boolalpha;

    // all works fine:
    ClassA a1(1, 2);
    ClassA a2(3, 4);

    std::cout << (*a1.wrapper == *a1.wrapper) << std::endl; // true
    std::cout << (*a2.wrapper == *a2.wrapper) << std::endl; // true
    std::cout << (*a1.wrapper == *a2.wrapper) << std::endl; // true

    // cause compilation error:
    ClassB b1(1, 2);
    ClassB b2(3, 4);

    std::cout << (*b1.wrapper == *b1.wrapper) << std::endl;
    std::cout << (*b2.wrapper == *b2.wrapper) << std::endl;
    std::cout << (*b1.wrapper == *b2.wrapper) << std::endl;

    return 0;
}

比较在ClassA实例中创建的lambda表达式总是返回true,即使它们在不同的上下文中创建(正如我所说)。另一方面,ClassB甚至无法编译,因为其lambda的运算符==!=未找到。
看起来,这个程序不太规范,以我尝试的方式比较lambda会导致程序的行为未定义。但如果确实是未定义的行为,它们怎么能被比较呢?(我猜,根本不能)
1个回答

4
对于这种没有捕获列表的lambda表达式,它们实际上并不是闭包,因此操作符==和!=与静态函数一样工作(好像编译器也将它们生成为静态函数)。
这是因为没有捕获的lambda的闭包类型提供了一个转换运算符,返回一个函数指针,这些指针是可以比较的。[expr.prim.lambda]/6(我的强调):
没有捕获的lambda表达式的闭包类型有一个公共的非虚拟非显式转换函数,其返回类型为指向具有与闭包类型的函数调用运算符相同参数和返回类型的函数的指针。此转换函数返回的值应为一个函数地址,当调用该函数时,具有与闭包类型的函数调用运算符相同的效果。
(如果转换运算符是显式的,则比较将无法工作)
粗略地说,形如[] {}的lambda表达式被翻译为:
struct closure_type
{
private:
    static void call() {}

public:

    // closure_type() = delete; // Commented for the sake of the demo
    closure_type& operator=(closure_type const&) = delete;

    void operator()() const { /*return call();*/ }

    operator decltype(&call)() const
    {
        return &call;
    }
};

如您所见,转换运算符每次返回相同的函数指针。尽管如果发生类似的情况将会非常让人惊讶,但标准确实允许调用转换运算符时为同一闭包对象返回不同的函数指针;因此,对两个闭包对象进行比较具有实现定义的值。(然而,对于相同类型的两个闭包对象,在所有实现中应该都为true。)


现在清楚了,为什么带有空捕获列表的lambda可以进行比较。但是,如何比较具有非空捕获列表的lambda的问题仍未得到解答。 - alphashooter
@alphashooter 你想要比较什么?也就是说,应该比较哪些 lambda 的属性? - Columbo
例如,lambda表达式是可复制的,但如果我有两个相同闭包的副本,我无法使用等号运算符检查它们是否相等。 - alphashooter
1
@alphashooter 不可能进行比较。你无法访问闭包的成员变量,编译器也不会为你声明任何 operator== - Columbo
好的,这就是我想知道的。看起来,唯一的解决方案是比较它们的地址,但这需要避免创建副本。 - alphashooter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接