4

Why can the compiler not select the most obvious overload:

#include <iostream>
#include <functional>

static void foo(const std::function<void(bool)>& f) {
    std::cerr << "using bool overload" << std::endl;
    f(true);
}

static void foo(const std::function<void(int)>& f) {
    std::cerr << "using int overload" << std::endl;
    f(1);
}

int main() {
    foo([](const bool value) {
        std::cout << std::boolalpha << value << std::endl;
    });
    foo([](const int value) {
        std::cout << value << std::endl;
    });
    return 0;
}

You would expect the output:

using bool overload
true
using int overload
1

However, the compiler cannot deduce the correct overload:

gcc-4.8:

main.cpp: In function 'int main()':
main.cpp:17:6: error: call of overloaded 'foo(main()::__lambda0)' is ambiguous
     });
      ^
main.cpp:17:6: note: candidates are:
main.cpp:4:13: note: void foo(const std::function<void(bool)>&)
 static void foo(const std::function<void(bool)>& f) {
             ^
main.cpp:9:13: note: void foo(const std::function<void(int)>&)
 static void foo(const std::function<void(int)>& f) {
             ^

clang-3.4:

main.cpp:15:5: error: call to 'foo' is ambiguous
    foo([](const bool value) {
    ^~~
main.cpp:4:13: note: candidate function
static void foo(const std::function<void(bool)>& f) {
            ^
main.cpp:9:13: note: candidate function
static void foo(const std::function<void(int)>& f) {
            ^

Is this because the std::function constructor automatically consumes and converts parameters?

Xeo
  • 129,499
  • 52
  • 291
  • 397
Matt Clarkson
  • 14,106
  • 10
  • 57
  • 85
  • "Is this because the std::function constructor automatically consumes and converts parameters?" `std::function` has a very greedy ctor template. Also, as there's a conversion from `bool` to `int` (and vice versa), both functions can be called with arguments of both types (that's why the conversion isn't refused for one of the overloads). – dyp Nov 12 '13 at 16:56
  • Can I write a overload that accepts lambdas/functors in a way that the overload correctly works? Edit: or write a `type_trait` that gets the parameter type? – Matt Clarkson Nov 12 '13 at 16:58
  • Like [this](http://coliru.stacked-crooked.com/a/854d7be9aeb9b00b)? (The implicit conversions inherited from C are often evil ;) – dyp Nov 12 '13 at 16:59
  • That works because the types are implicitly convertible to each other so the function ctor cannot convert, right? I really need it to work with the primitive types. – Matt Clarkson Nov 12 '13 at 17:02
  • possible duplicate of [Isn't the template argument (the signature) of std::function part of its type?](http://stackoverflow.com/questions/5931214/isnt-the-template-argument-the-signature-of-stdfunction-part-of-its-type) – Xeo Nov 12 '13 at 17:07
  • 1
    Note that even with the fix mentioned in the answers on the duped question, your example wouldn't work because `int` and `bool` are convertible to each other. – Xeo Nov 12 '13 at 17:08
  • That works because the types are **not** implicitly convertible to each other, yes ;) If you can change the signature of `foo`, I'd make it a template (so that the lambda-expression doesn't need to be wrapped in `std::function`) and either use tag-dispatch or specialization (on an *additional* template parameter) to provide different functionality for different "argument types". – dyp Nov 12 '13 at 17:09
  • @Xeo, I guess I should clarify the situation: all I need to know is the type of the first argument to the overload. Working out the answer from [this](http://stackoverflow.com/questions/6667449) – Matt Clarkson Nov 12 '13 at 17:11
  • @Xeo: I agree that the possible duplicate question is very relevant to this one but it's not really a duplicate. (Exactly because of you said on `int` and `bool` being convertible to each other.) In addition, the code in the old question compiles fine with gcc 4.8.1. So I guess, there was a bug in gcc/libstdc++ that has been fixed since then. (I haven't tried other compilers though.) – Cassio Neri Nov 12 '13 at 17:32
  • @Cassio: It was not a bug, the standard specifies `std::function`'s constructor like that. Post-C++11 there's a DR with a proposed fix that makes the original code in the duped question unambiguous. Also, note that this question only asks of the cause, not of the fix. That should be a seperate question (and likely also a duplicate). – Xeo Nov 12 '13 at 17:51
  • 1
    From cppreference: "The lambda expression constructs an unnamed temporary object of unique unnamed non-union non-aggregate type, known as closure type ... ". This seems to be the key to the problem ;) – BlackCat Nov 12 '13 at 18:07
  • @Xeo Agreed (thanks). For the record this is the [DR](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2132) that you mentioned. – Cassio Neri Nov 12 '13 at 18:18

2 Answers2

0

So to find the type of the lambda, this worked:

#include <iostream>
#include <functional>
#include <type_traits>

template<typename F>
struct first_argument {
  template<typename Ret, typename A, typename... Rest>
  static A  helper(Ret (F::*)(A, Rest...));
  template<typename Ret, typename A, typename... Rest>
  static A helper(Ret (F::*)(A, Rest...) const);
  typedef decltype(helper(&F::operator())) type;
};

template <typename T>
static void foo(T f) {
    typedef typename first_argument<T>::type type;
    std::cerr << "using traits overload" << std::endl;
    std::cerr << "typename: " << typeid(type).name() << std::endl;
    f(true);
}

template<typename T>
static void foo(void (*f)(const T)) {
    std::cerr << "using function pointer overload" << std::endl;
    std::cerr << "typename: " << typeid(T).name() << std::endl;
    f(T());
}


int main() {
    const bool x = false;
    foo([](const bool value) {
        std::cout << std::boolalpha << value << std::endl;
    });
    const int i = 9;
    foo([i](const bool value) {
        std::cout << std::boolalpha << value << ':' << i << std::endl;
    });
    foo(+[](const bool value) {
        std::cout << std::boolalpha << value << std::endl;
    });
    foo(+[](const int value) {
        std::cout << value << std::endl;
    });
    foo([&x](const bool value) {
        std::cout << std::boolalpha << value << '|' << x << std::endl;
    });
    return 0;
}

Run it here

Matt Clarkson
  • 14,106
  • 10
  • 57
  • 85
  • This only works for non-capturing lambda's. Lambda's that capture _anything_, even a simple `int` cannot be converted to function pointers. – MSalters Nov 13 '13 at 13:14
  • @MSalters, huh? It works fine. Have updated the answer. It _doesn't_ work with `std::bind` though and fails when used as a template trait type. – Matt Clarkson Nov 13 '13 at 13:25
  • The influx of this kind of questions is driving me insane NIPPLE SALADS! but I think "finding the type of the lambda" is the wrong problem :< (Note how I said wrong *problem*; I don't even want to look at the solutions) – R. Martinho Fernandes Nov 13 '13 at 14:03
0

Lambda's may have a funky syntax, but they're still functors. That is to say, they define an unique_type::operator()(Args...). This is also true for capturing lambda's; the captured values are members of the lambda object.

So, the following bit of code breaks down a lambda object:

template<typename T, typename R, typename A>
void bar(T const& t, R (T::*pmf)(A));

template<typename LAMBDA>
void foo(LAMBDA const& l)
{
  bar(l, &LAMBDA::operator());
}

Obviously, if you just need to distinguish between the two lambda's in your example, you can replace bar with two non-templatized functions. OTOH, if you need to support lambda's with any number of arguments, you'll need a variadic template for bar.

MSalters
  • 173,980
  • 10
  • 155
  • 350