5

The code below doesn't compile on gcc 4.5 because the call to foo is ambiguous. What is the correct way to disambiguate it?

#include <iostream>
#include <functional>
using namespace std;

void foo(std::function<void(int, int)> t)
{
    t(1, 2);
}

void foo(std::function<void(int)> t)
{
    t(2);
}

int main()
{
    foo([](int a, int b){ cout << "a: " << a << " b: " << b << endl;});
}
Chubsdad
  • 24,777
  • 4
  • 73
  • 129
Ted
  • 53
  • 2

2 Answers2

6

The best way is to explicitly create a std::function object of the correct type then pass that object to the function:

std::function<void(int, int)> func = 
    [](int a, int b) { cout << "a: " << a << " b: " << b << endl; }
foo(func);

or inline:

foo(
    std::function<void(int, int)>(
        [](int a, int b) { cout << "a: " << a << "b: " << b << endl; }
));

std::function has a constructor template that accepts anything:

template<class F> function(F);

Because of this, there's no way for the compiler to know during overload resolution which foo to select: both std::function<void(int)> and std::function<void(int, int)> have a constructor that can take your lambda expression as an argument.

When you pass a std::function object directly, the std::function copy constructor is preferred during overload resolution, so it is selected instead of the constructor template.


Answer for the future: If the capture list is guaranteed to be empty, you can also use ordinary function pointers. In C++0x, a captureless lambda is implicitly convertible to a function pointer. So, you can use something like

void foo(void (*t)(int, int)) { t(1, 2); }

void foo(void (*t)(int)) { t(1); }

and call foo directly with the captureless lambda (or a function pointer with matching type).

Note that this conversion is a very recent addition to the draft language standard (it was added in February of this year), so it is not likely to be widely supported yet. Visual C++ 2010 doesn't support it yet; I don't know about the latest g++.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • Isn't there some factory function for `std::function`, since type inference is allowed on template functions but not template classes? – Ben Voigt Nov 06 '10 at 03:02
  • @Ben: I don't think a factory function is viable in this case. Consider a function object that has two `operator()` overloads; how should the `std::function` type be selected? – James McNellis Nov 06 '10 at 03:06
  • @James: oops, forgot that even a non-capturing lambda is a function object. Is there a factory function from function pointers, or have I totally lost it? – Ben Voigt Nov 06 '10 at 03:16
  • @Ben: I was just scanning the C++0x draft spec and didn't see anything. – James McNellis Nov 06 '10 at 03:18
  • @James: Thanks for the reply; that certainly clears up why it is happening. Since in my case the difference between overloads will always be the number of the parameter do you happen to know any way to use SFINAE to choose the correct function based on that. – Ted Nov 06 '10 at 03:27
  • @Ted: Is the capture list always empty? (i.e., is there always nothing inside of `[]`?) – James McNellis Nov 06 '10 at 03:33
  • @James: I currently can't think of a reason why I would need a non empty list for these functions. – Ted Nov 06 '10 at 03:39
  • @Ted: I added a note about what you can do (someday). I don't know whether gcc supports it, you'll have to test it. I know for sure Visual C++ 2010 doesn't (not their fault; the standard was updated right before they released). – James McNellis Nov 06 '10 at 03:54
  • @Ted: Out of curiosity, does the function pointer conversion work with g++ 4.5? – James McNellis Nov 06 '10 at 05:36
3

I've recently been thinking about a similar problem and when looking around for any known solutions I came across this post and lack of solutions for resolving

An alternative solution is to abstract over the functor as a template argument and use decltype to resolve its type. So, the above example would become:

#include <iostream>
#include <functional>
using namespace std;

template<class F>
auto foo(F t) -> decltype(t(1,2))
{
    t(1, 2);
}

template<class F>
auto foo(F t) -> decltype(t(2)) 
{
    t(2);
}

int main()
{
     foo([](int a, int b){ cout << "a: " << a << " b: " << b << endl;});
}

This works as expected with gcc 4.5.

pasternak
  • 103
  • 4