Given the following code, what is the reason behind the ambiguity? Can I circumvent it or will I have to keep the (annoying) explicit casts?
#include <functional>
using namespace std;
int a(const function<int ()>& f)
{
return f();
}
int a(const function<int (int)>& f)
{
return f(0);
}
int x() { return 22; }
int y(int) { return 44; }
int main()
{
a(x); // Call is ambiguous.
a(y); // Call is ambiguous.
a((function<int ()>)x); // Works.
a((function<int (int)>)y); // Works.
return 0;
}
Interestingly, if I comment out the a()
function with the function<int ()>
parameter and call a(x)
in my main, the compilation correctly fails because of the type mismatch between x
and the argument function<int (int)>
of the only a()
function available. If the compiler fails in that case, why would there be any ambiguity when the two a()
functions are present?
I've tried with VS2010 and g++ v. 4.5. Both give me the exact same ambiguity.
The problem is that both
function<int()>
andfunction<int(int)>
are constructible from the same function. This is what the constructor declaration ofstd::function
looks like in VS2010:Ignoring the SFINAE part, it is constructible from pretty much anything.
std::/boost::function
employ a technique called type erasure, to allow arbitary objects/functions to be passed in, so long they satisfy the signature when being called. One drawback from that is, that you get an error in the deepest part of the implementation (where the saved function is being called) when supplying an object which can't be called like the signature wants it to, instead of in the constructor.The problem can be illustrated with this little class:
Now, when the compiler searches for valid functions for the overload set, it tries to convert the arguments if no perfect fitting function exists. The conversion can happen through the constructor of the parameter of the function, or through a conversion operator of the argument given to the function. In our case, it's the former.
The compiler tries the first overload of
a
. To make it viable, it needs to make a conversion. To convert aint(*)()
to amyfunc<int()>
, it tries the constructor ofmyfunc
. Being a template that takes anything, the conversion naturally succeeds.Now it tries the same with the second overload. The constructor still being the same and still taking anything given to it, the conversion works too.
Being left with 2 functions in the overload set, the compiler is a sad panda and doesn't know what to do, so it simply says the call is ambigious.
So in the end, the
Signature
part of the template does belong to the type when making declarations/definitions, but doesn't when you want to construct an object.Edit:
With all my attention on answering the title-question, I totally forgot about your second question. :(
Afaik, you have 3 options.
Make a
function
object of the appropriate type and pass thatfunction<int()> fx = x; function<int(int)> fy = y; a(fx); a(fy);
Hide the tedious casting in a function and use TMP to get the right signature
The TMP (template metaprogramming) version is quite verbose and with boilerplate code, but it hides the casting from the client. An example version can be found here, which relies on the
get_signature
metafunction that is partially specialized on function pointer types (and provides a nice example how pattern matching can work in C++):Of course, this needs to be extended for the number of arguments you want to support, but that is done once and then buried in a
"get_signature.h"
header. :)Another option I consider but immediatly discarded was SFINAE, which would introduce even more boilerplate code than the TMP version.
So, yeah, that are the options that I know of. Hope one of them works for you. :)
I've seen this question come up one too many times. libc++ now compiles this code without ambiguity (as a conforming extension).
Overdue Update
This "extension" proved sufficiently popular that it was standardized in C++14 (though I was not personally responsible for getting that job done).
In hindsight, I did not get this extension exactly correct. Earlier this month (2015-05-09) the committee voted in LWG issue 2420 which effectively changes the definition of Callable so that if the
std::function
has avoid
return type it will ignore the return type of the wrapped functor, but still otherwise consider it Callable if everything else matches up, instead of considering it not Callable.This post-C++14 tweak does not impact this particular example since the return types involved are consistently
int
.Here's an example of how to wrap
std::function
in a class that checks invokability of its constructor parameters:Use like this:
Note that this isn't quite the same as overloading on function signature, as it treats convertible argument (and return) types as equivalent. For exact overloading, this should work:
std::function<T>
has a conversion ctor that takes an arbitrary type (i.e., something other than aT
). Sure, in this case, that ctor would result in a type mismatch error, but the compiler doesn't get that far -- the call is ambiguous simply because the ctor exists.