11

Possible Duplicate:
Disambiguating calls to functions taking std::functions
Isn't the template argument (the signature) of std::function part of its type?

I want to overload a function so that it can be called with a variety of different lambdas (generally with more or fewer arguments) naturally. The obvious thing I tried was:

#include <functional>
#include <iostream>

extern void fn(std::function<void(int)>);
extern void fn(std::function<void(int, int)>);

void test()
{
    fn([](int a) { std::cout << "lambda with 1 arg " << a << std::endl; });
}

However, this fails with g++ (tried v4.6.2 and v4.7.1) with the error:

test.cc: In function ‘void test()’:
test.cc:9:74: error: call of overloaded ‘fn(test()::<lambda(int)>)’ is ambiguous
test.cc:9:74: note: candidates are:
test.cc:4:13: note: void fn(std::function<void(int)>)
test.cc:5:13: note: void fn(std::function<void(int, int)>)

Now I found an alternate (and much more complex) approaches here and here, but my question is, why does the above code fail? Is there something in the standard that says it can't work, or is this merely a bug/limitation of g++?

Community
  • 1
  • 1
Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • @JamesMcNellis: Not a duplicate -- that question is about alternatives to overloading simply. My question is _why_ doesn't it work -- is it in the spec that it can't work, or just a limitation of g++?. – Chris Dodd Aug 20 '12 at 23:03
  • My answer there explains why: there is an implicit conversion from any type to `std::function`. – James McNellis Aug 20 '12 at 23:05
  • @JamesMcNellis: That answer seems to be incorrect, at least in the case of g++, since the ctor for std::function can't take a type that doesn't have a matching operator() and gives ` no match for call to ‘(test()::) (int, int)` if you try it. – Chris Dodd Aug 21 '12 at 00:13
  • 1
    @ChrisDodd that error occurs in the body, which is only checked *after* one conversion is picked. libstdc++ is a bit annoying in this part. libc++ compiles this right. – R. Martinho Fernandes Aug 21 '12 at 10:56

2 Answers2

5

Every Lambda [](int a) { std::cout << "lambda with 1 arg " << a << std::endl; } has unique type even another lambda same as above will result in different lambda type with member operator()(int a)

Your implementation of std::function has a templated conversion that can be used by both std::function<void(int)> and std::function<void(int, int)>. While only one of them compiles when instantiated, they're both considered for overload resolution, and that's what creates the ambiguity. To get the desired result the library needs to employ SFINAE to exclude the erroneous one from the overload candidate set (recent versions of libc++ do that).

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
Mr.Anubis
  • 5,132
  • 6
  • 29
  • 44
  • So this `std::function f = [](int) { };` should compile? – mfontanini Aug 18 '12 at 19:02
  • 1
    @mfontanini: I have a suspicion that the (illegal) conversion you suggest is not actually excluded from the overload set of the `std::function` template. – Kerrek SB Aug 18 '12 at 19:02
  • The thing is, a lambda can't be converted to a std:function that doesn't match the argument types -- if you try it you'll get a complex error message about the argument mismatch. – Chris Dodd Aug 20 '12 at 23:04
0

The question sounds inside-out. You define a type using std::function to describe how you're going to call objects of that type and what their return value is. You can then use that specialization of std::function to wrap various callable objects, including lambdas, that have different argument types or a different return type.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165