Lambda and the Equality / Inequality Operator

I have a question about lambdas equality comparison. I tried to read some links but didn't find anything about it.

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

      

For this type, lambdas, which are not closures in fact, because they have an empty capturing list, operators ==

and !=

work the same way as for static functions (well, the compiler seems to generate them as static functions as well). But for closures, any comparison attempt equally throws a compilation error.

Here's a simple program like:

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

      

Comparison of lambdas created on ClassA instances will always return true

, even if created in a different context (as I said). On the other hand, ClassB not even compile, because the operator ==

and !=

for its Lambda is not found.

This program doesn't seem to be well-formed and the comparison of lambdas in the way I've tried causes the program to behave undefined. But if this is indeed undefined behavior, how can they be compared? (I think not)

+3


source to share


1 answer


For this type of lambdas that are not actually closures because they have an empty capture list, the == and! = Operators work the same as for static functions (well, the compiler seems to generate them as static functions).

This works because the uncaptured lambda closure type provides a conversion operator that returns a function pointer. It's comparable. [expr.prim.lambda] / 6 (attention my):

The closure type for a lambda expression without lambda capturing has a public non-virtual implicit const conversion function for a function pointer that has the same parameter and return types as the function call function closure. The return value of this conversion function must be the address of a function that, when called, has the same effect as a function call such as a closing operator call.



(If the conversion operator was explicit, the comparison will not work)
Roughly, the form lambda [] {}

translates to

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

      

As you may have noticed, the conversion operator returns the same function pointer every time. While it would be completely unexpected if something like this happened, the standard allows different function pointers to be returned to invoke a conversion operator on the same closure object; Therefore, comparing two closure objects is implementation-defined. (However, for all implementations, there must be true

closures of the same type for two objects.)

+4


source







All Articles