lambdas和等式/不等式算子

lambdas and equality/inequality operator

本文关键字:不等式 lambdas      更新时间:2023-10-16

我有一个关于lambdas的等式比较的问题。我试着阅读了一些参考资料,但没有发现任何关于这方面的信息。

[] (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的运算符==!=

这个程序的形式似乎不太好,以我尝试的方式比较lambdas会导致程序的行为不明确。但是,如果这真的是未定义的行为,如何将它们进行比较?(我想,没关系)

对于这种类型的lambda,它们实际上不是闭包,因为它们捕获列表为空,运算符==和!=工作方式与对于静态函数(看起来,编译器将它们生成为静态函数)。

它之所以有效,是因为没有捕获的lambda的闭包类型提供了一个返回函数指针的转换运算符。这些是可比较的。[expr.prim.lambda]/6(强调矿):

没有lambda捕获的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。)