C++ Overloaded function call is ambiguous

86 Views Asked by At

I was playing around with C++ std::function objects, and wanted to add some functionality to them, so I subclassed them. However, when I tried to create a function that overloaded based on the signature of the function that was passed to it, the compiler told me that the call was ambiguous.

Can someone please help me to understand where the ambiguity lies, and also what I could do to disambiguate it?

Here's the sample code:

#include <functional>
#include <iostream>

class A0 : public std::function<void()>
{
public:
    template <typename T>
    A0(const T& t)
        : std::function<void()> { t }
    {
    }
};

template <typename T1>
class A1 : public std::function<void(T1)>
{
public:
    template <typename T>
    A1(const T& t)
        : std::function<void(T1)> { t }
    {
    }
};

void doThing(const A0& a0)
{
    std::cout << "Do A0 thing\n";
}

void doThing(const A1<int>& a1)
{
    std::cout << "Do A1 thing\n";
}

int main()
{
    doThing([] (int i) {});

    return 0;
}

And here's the compiler output:

g++ .\sources\Sample.cpp            
.\sources\Sample.cpp: In function 'int main()':
.\sources\Sample.cpp:37:12: error: call of overloaded 'doThing(main()::<lambda(int)>)' is ambiguous
   37 |     doThing([] (int i) {});
      |     ~~~~~~~^~~~~~~~~~~~~~~
.\sources\Sample.cpp:25:6: note: candidate: 'void doThing(const A0&)'
   25 | void doThing(const A0& a0)
      |      ^~~~~~~
.\sources\Sample.cpp:30:6: note: candidate: 'void doThing(const A1<int>&)'
   30 | void doThing(const A1<int>& a1)
      |      ^~~~~~~
1

There are 1 best solutions below

0
Jan Schultke On

Both A0 and A1 have a constructor that accepts any type const T&. The lambda expression [] (int i) {} could be used for either constructor, so neither wins in overload resolution.

It doesn't really make sense that your constructor accepts any function object, because std::function already supports this. Instead, accept the right std::function in your constructor:

class A0 : public std::function<void()>
{
public:
    // option 1: take ownership of an existing std::function
    A0(std::function<void()> t)
        : std::function<void()>{ std::move(t) }
    {}
};

template <typename T1>
class A1 : public std::function<void(T1)>
{
public:
    // option 2: inherit all of the constructors from std::function
    using std::function<void(T1)>::function;
};

See live example on Compiler Explorer

This solves the problem because constructing a std::function<void()> from the lambda expression is impossible, so only the constructor of A1 participates in overload resolution. You can use option 1 or option 2, it's up to you. The choice depends on whether you want all of the constructors.


Note: inheriting from standard library types is generally not a good idea. It's technically allowed, but they aren't meant to be used like this. See Extending the C++ Standard Library by inheritance?