3

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)
      |      ^~~~~~~
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    Both of your classes have constructors that accept _any_ type as argument and they have the exact same signatures. Why do you expect one to be favoured over the other? (The problem is completely independent of the `std::function` subclassing btw. You can remove that and you'll have the same ambiguity.) – user17732522 Jun 30 '23 at 22:58
  • The easiest way to remove the ambiguity is to change one of names from `doThing` to `doOtherThing`. – Eljay Jun 30 '23 at 23:11

1 Answers1

3

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?

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96