c++c++11overloadingstd

C++11 auto, std::function and ambiguous call to overloaded function


I'm wondering if anyone knows why the following sample doesn't compile giving an ambiguous call to overload function error. If I replace the auto with a strongly typed functor signature, it then is able to properly distinguish between the two method overloads.

I noticed the same issue doesn't occur when not using std::function as my overload arguments. If my overloads take just a simple float and int, the compiler can properly distinguish between the two overloads even when using the auto keyword to define my input arguments. I'm compiling this in VisualStudio 2012. Could this just be an bug in the VS compiler? I don't have access to a machine with GCC or Clang on it right now, but does anyone know if this would compile there?

Compile Error: ambiguous call to overload function

class AmbiguousOverload
{
public:
    static void OverloadedMethod(std::function<int()>) {}
    static void OverloadedMethod(std::function<float()>) {}
};

int _tmain(int argc, _TCHAR* argv[])
{
    auto func1 = []() -> float {
        return 0.5f;
    };

    auto func2 = []() -> int {
        return 12;
    };

    AmbiguousOverload::OverloadedMethod(func1);
    AmbiguousOverload::OverloadedMethod(func2);

    return 0;
}

Compiles

class AmbiguousOverload
{
public:
    static void OverloadedMethod(std::function<int()>) {}
    static void OverloadedMethod(std::function<float()>) {}
};

int _tmain(int argc, _TCHAR* argv[])
{
    std::function<float()> func1 = []() -> float {
        return 0.5f;
    };

    std::function<int()> func2 = []() -> int {
        return 12;
    };

    AmbiguousOverload::OverloadedMethod(func1);
    AmbiguousOverload::OverloadedMethod(func2);

    return 0;
}

Also Compiles

class AmbiguousOverload
{
public:
    static void OverloadedMethod(int) {}
    static void OverloadedMethod(float) {}
};

int _tmain(int argc, _TCHAR* argv[])
{
    auto v1 = 0.5f;
    auto v2 = 12;

    AmbiguousOverload::OverloadedMethod(v1);
    AmbiguousOverload::OverloadedMethod(v2);

    return 0;
}

Solution

  • std::function had a greedy template constructor that will attempt to construct it from anything at all you passed it1. std::function is not very suitable for use in overload resolution. It worked in one case because perfect matches are preferred to conversion, but as noted it is fragile.

    Note that lambdas and std::function objects are unrelated types. std::function knows how to wrap a lambda, but it can wrap any copyable invokable object. Lambdas are auto-created anonymously named classes that are copyable and invokable. std::function is a class designed to type-erase invocation.

    Imagine if your overrides took short and long. The auto x = 2.0 and short s = 2.0 would correspond to the auto x = lambda and std::function<blah> f = lambda cases. When you pick the type explicitly you cause a type conversion, and the types you picked explicitly had no ambiguity. But when you did auto it took the real type -- and the real type was ambiguous.

    SFINAE or tag dispatching using std::result_of would let you handle these overrides manually.

    In this changed a bit. Now the constructor only tries to swallow compatible arguments. But a function returning int and one returning float are both compatible with each other, so it won't help your specific case.


    1 To a certain extent this is a flaw in std::function. Its "universal" constructor should really only participate in overload resolution when the type passed in is both copyable and std::result_of_t< X( Args... ) > can be converted to the result type of the std::function. I suspect post-concepts this will be added to the standard, as post-concepts this is really easy to both write and express (and very little conforming code will be broken by it). However, in this particular case, this would not actually help, as int and float can be converted into each other, and there are no ways to say "this constructor will work, but really it isn't a preferred option".