0

I am using the following function to test equality of std::functions. The function is inspired from the SO discussion here

template<typename T, typename... U>
inline bool AreEqual(std::function<T(U...)> function_1, std::function<T(U...)> function_2) {

    typedef T(fnType)(U...);
    fnType ** f_ptr_1 = function_1.template target<fnType*>();
    size_t f1 = (size_t) *f_ptr_1;
    fnType ** f_ptr_2 = function_2.template target<fnType*>();
    size_t f2 = (size_t) *f_ptr_2;

    return (f1 == f2);
}

Now if I have the below test to verify that its working

#include "gtest/gtest.h"

void DummyFunc(bool value) {
    value = true;
}

TEST(Some_Test, verify_equality_of_std_functions) {
    typedef std::function<void(bool)> FuncType1;

    FuncType1 f2 = &DummyFunc;
    EXPECT_TRUE(AreEqual(f2, f2));  // This passes as expected

    auto test_lambda = [](bool dummy_param) {
        dummy_param = true;
    };

    FuncType1 f1 = test_lambda;
    EXPECT_TRUE(AreEqual(f1, f1));  // But, this causes a crash! Why?
}

Why does AreEqual crash when passing a lambda? Am i doing something wrong with the lambda or is it that AreEqual lacks logic to compare lambdas stored in std::functions?

cigien
  • 57,834
  • 11
  • 73
  • 112
TheWaterProgrammer
  • 7,055
  • 12
  • 70
  • 159
  • Did you try a debugger? – Passer By Sep 09 '20 at 15:52
  • `f1` is null when I assign `test_lambda` to it. Isnt it correct with the way I am assigning the lambda to `f1`? – TheWaterProgrammer Sep 09 '20 at 15:57
  • 3
    Then you identified the crash. Now your question is vastly different: "why `std::function{[]{}}.target == nullptr`"? – Passer By Sep 09 '20 at 15:59
  • The answer to which is a lambda is not a function pointer. – Passer By Sep 09 '20 at 16:00
  • Aha! Lambda is not a function pointer? But wait, I can assign a lambda to a `std::function`. Right? Its still not a function pointer? I need some reading to get the fundamentals right here. – TheWaterProgrammer Sep 09 '20 at 16:16
  • A capture-less [lambda](https://en.cppreference.com/w/cpp/language/lambda) can be converted to a function pointer. – Some programmer dude Sep 09 '20 at 16:19
  • 2
    There is no way to compare two `std::function` objects without knowing what type of value is in them. They can hold any callable value, which includes function pointers, lambdas, objects with `operator()`, bind expressions... – cdhowie Sep 09 '20 at 16:28
  • Ok. Thanks a lot for explaining this out. – TheWaterProgrammer Sep 09 '20 at 16:48
  • 1
    If there were a way of comparing general `std::function`s for equality, it'd have an `operator==`. There isn't, so there isn't. – Caleth Sep 09 '20 at 16:57
  • @Caleth It is possible if the underlying callable can be compared for equality. However, it's unlikely that it would _actually be useful_ to enough people for the committee to make it part of the standard. – cdhowie Sep 10 '20 at 00:17
  • @Caleth [Here](http://coliru.stacked-crooked.com/a/52a4eb79bbff0777) is a (probably bad) example of what I mean. It provides a polymorphic function wrapper that can be compared. Under the hood this is done by first ensuring that both objects wrap a value of the same type. If they do, the equality test is dispatched virtually and the result is either false (if the actual type both functions represent has no equality operator) or the result of calling that operator. There's no reason `std::function` couldn't do the same thing, I'm just not sure it's actually useful in the real world. – cdhowie Sep 10 '20 at 05:00
  • Perhaps the reason they chose not to go this route is that it has surprising consequences. In particular, since lambdas cannot be compared, comparing a function wrapper of a lambda to itself evaluates as false. Perhaps the equality operation could return a tristate (is equal, is not equal, underlying types are the same but not comparable), but this is approaching the level of unclean/ridiculous that tends to scare away standards authors, and rightly so. Nonetheless, it _is_ possible to accomplish. – cdhowie Sep 10 '20 at 05:04
  • @cdhowie but it isn't possible for *all* things you can put into `std::function`. Your implementation will say some things are unequal to themselves, which *isn't* an equivalence relation. – Caleth Sep 10 '20 at 07:36
  • @Caleth Right, which I said myself in my last comment. An exception or tristate would be one way around this, but that's weird enough that it wouldn't be likely to be standardized. – cdhowie Sep 10 '20 at 16:11
  • @cdhowie If you name it `operator==`, you've now broken generic code that has the *eminently reasonable* assumption that `==` is an equivalence relation, emulating one of Javascript's worst warts. – Caleth Sep 10 '20 at 17:00

1 Answers1

2

You can use the following function to do the exact.

template<class RET, class... Args>
inline bool AreEqual(RET(*f1)(Args&...), RET(*f2)(Args&...))
{
    return f1 == f2;
}

TEST(Some_Test, verify_equality_of_std_functions) {
    typedef std::function<void(bool)> FuncType1;

    FuncType1 f2 = DummyFunc;
    EXPECT_TRUE(AreEqual(*f2.target<void(*)()>(), *f2.target<void(*)()>())); 

    auto test_lambda = [](bool dummy_param) {
        dummy_param = true;
    };
    
    /* Lambdas are required to be converted to raw function pointers. */
    void(*f1)() = test_lambda;
    EXPECT_TRUE(AreEqual(f1, f1));
}

Lambdas are not std::function<>s.

TheWaterProgrammer
  • 7,055
  • 12
  • 70
  • 159
D-RAJ
  • 3,263
  • 2
  • 6
  • 24