lambdas和平等/不平等运算符

时间:2014-11-09 14:44:40

标签: c++ c++11 lambda closures

我有一个关于lambda的相等比较的问题。 我试过阅读一些参考资料,但我一无所获。

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

对于这种类型的lambdas,实际上因为它们有空捕获列表而不是闭包,运算符==!=的工作方式与静态函数相同(好吧,似乎编译器会生成它们也是静态函数)。但是对于闭包,任何以相同方式进行比较的尝试都会导致编译错误。

这是一个简单的程序,例如:

#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 个答案:

答案 0 :(得分:4)

  

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

它的工作原理是因为没有捕获的lambda的闭包类型提供了一个返回函数指针的转换操作符。那些是可比的。 [expr.prim.lambda] / 6(强调我的):

  

lambda-expression 的闭包类型,没有 lambda-capture   具有公共非虚拟非显式 const转换函数   指向具有相同参数和返回类型的函数的指针   闭包类型的函数调用操作符。这个值返回的值   转换函数应该是函数的地址,当时   invoked,与调用闭包类型的函数具有相同的效果   呼叫运营商。

(如果转换运算符是明确的,则比较不起作用)
粗略地说,[] {}形式的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。)