Search code examples
c++overloadingtype-deduction

Compiler fails to deduce types when using std::function


So, I have a method and its overload for a class, written like this:

bool my_for_each(someIterator begin, someIterator end, bool (*_lambda)(someIterator));

void my_for_each(someIterator begin, someIterator end, void (*_lambda)(someIterator));

As you can see, the only difference is the signature of a passed function, that is, its return type. Although the code above works perfectly fine when i call it:

my_for_each(iteratorBegin, iteratorEnd, [](someIterator) {; }); // calls void version

my_for_each(iteratorBegin, iteratorEnd, [](someIterator)->bool {return false; }); // calls bool version

..., if I write the my_for_each function as follows:

bool my_for_each(someIterator begin, someIterator end, std::function<bool(someIterator)> _lambda);

void my_for_each(someIterator begin, someIterator end, std::function<void(someIterator)> _lambda);

The code fails to compile if i call the function the same way (C2668 ambiguous call to overloaded function). Although, if I manually cast the function:

my_for_each(iteratorBegin, iteratorEnd, static_cast<std::function<void(someIterator)>>([](someIterator) {; })); //calls void version

my_for_each(iteratorBegin, iteratorEnd, static_cast<std::function<bool(someIterator)>>([](someIterator) -> bool { return false; })); //calls bool version

The code works perfectly fine. So I'm just wondering:

  1. Why is normal function pointer type deduction "stronger" than the std template one?
  2. Is there some kinda workaround to still use the more generic version, while not casting the parameter manually?

The compiler is VS2015.

Thanks and have a nice day!


Solution

  • std::function and lambdas have distinct types, meaning the 2nd example requires an implicit conversion from the lambda's type to std::function.

    The problem is that the lambda that returns bool can be implicitly converted to both std::function<bool(T)> and std::function<void(T)>, thus both overloads are equally valid choices for your call, leading to an ambiguity error. When you manually cast them to the proper std::function the ambiguity is explicitly resolved.

    Edit : Workaround

    You can solve this problem by providing an addition templated overload that accepts any kind of callable type, deduces the return type for that callable type and preforms the cast automatically. I've changed someIterator to a template argument for generality. Since you haven't provided implementation details for your my_for_each functions, I have omitted those implementations. Since it appears that you only want to support void and bool return types, I've added a static_assert to generate a clear compiler error in case unsupported return types are provided.

    #include <functional>
    #include <type_traits>
    
    // Implement for std::function<bool(iter)>
    template<class I>
    bool my_for_each(I begin, I end, std::function<bool(I)> lambda);
    
    // Implement for std::function<void(iter)>
    template<class I>
    void my_for_each(I begin, I end, std::function<void(I)> lambda);
    
    // Dispatch to the right overload
    template<class T, class I>
    auto my_for_each(I begin, I end, T&& lambda)
    {
        using return_type = decltype(lambda(begin));    // Obtain the return type of lambda
        static_assert(std::is_same<return_type, bool>::value || std::is_same<return_type, void>::value,
            "my_for_each only accepts function objects that return void or bool");
        using function_type = std::function<return_type(I)>;    // The type to cast lambda to
        return my_for_each(begin, end, function_type(std::forward<T>(lambda))); // Preform the cast
    }