Reputation: 314
I have a question about equality comparison of lambdas. I've tried to read some references but I've found nothing about this.
[] (Args ...args) -> ReturnType { ... };
For this type of lambdas, which are not closures actually because they have empty capture list, operators ==
and !=
works in the same way as for static functions (well, it seems, compiler generates them as static functions as well). But for closures any attempt to compare in the same way causes compilation error.
Here is simple programm for example:
#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;
}
Comparing lambdas created in instances of ClassA always return true
even if they are created in different context (just as I said). On the other hand, ClassB do not even compile because operator ==
and !=
for its lambda is not found.
It seems, this programm is not well-formed, and comparing of lambdas in the way I've tried causes undefined behavior of the programm. But if it's really undefined behavior, how can they be compared? (I guess, nohow)
Upvotes: 4
Views: 285
Reputation: 60999
For this type of lambdas, which are not closures actually because they have empty capture list, operators == and != works in the same way as for static functions (well, it seems, compiler generates them as static functions as well).
It works because the closure type of a lambda without captures provides a conversion operator that returns a function pointer. Those are comparable. [expr.prim.lambda]/6 (emphasis mine):
The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator.
(If the conversion operator was explicit the comparison would not work)
Roughly, a lambda of the form [] {}
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 noted, the conversion operator returns the same function pointer each time. Though it would be utterly surprising if anything the like happened, the standard does allow different function pointers to be returned for a call to the conversion operator for the same closure object; A comparison of two closure objects has therefore an implementation-defined value. (It should, though, on all implementations be true
for two closure objects of the same type.)
Upvotes: 4