Templated constructor ambiguity when calling overloaded function with castable type

Viewed 113

I have 2 classes that both have single-argument templated constructors. One is meant as a catch all for integer types, and in the other class it's for binding any iterable object. I have two overloads for a particular function that will each of these types. If I call the function with an integer type or a string, or something that would work for at least one of the classes, I get an error about call ambiguity.

#include <string>

class A {
public:
    template <typename Iterable>
    A(Iterable it) : s(it.begin(), it.end()) {} 
private:
    std::string s;
};

class B {
public:
    template <typename Integer>
    B(Integer i) : i(i + 1) {}
private:
    int i;
};

void Use(A a)
{
   // some thing
}

void Use(B b)
{
    // some other thing
}

int main(void)
{
    Use(0);
    return 0;
}

The compiler doesn't seem to be looking far enough into the set of polymorphisms to determine that there really only is one possible solution. Could this be because template are 'resolved' before function overloads? How do I give the compiler some help?

2 Answers

The compiler doesn't seem to be looking far enough into the set of polymorphisms to determine that there really only is one possible solution.

Note that overload resolution is performed based on the signature of function templates, including function names, function parameters, template parameters, etc; but not implementations (e.g. the function body), which won't be examined during overload resolution.

You could apply SFINAE to put restrictions on types which could be accepted by constructor templates, by adding another template parameter with default value. e.g.

template <typename Iterable, typename = std::void_t<decltype(std::declval<Iterable>().begin()),
                                                    decltype(std::declval<Iterable>().end())>>
A(Iterable it) : s(it.begin(), it.end()) {} 

and

template <typename Integer, typename = std::void_t<decltype(std::declval<Integer>() + 1)>>
B(Integer i) : i(i + 1) {} 

LIVE

The compiler doesn't regard the implementation of the method when follows the SFINAE rules. In other words it sees the declaration of the class A constrator that accepts a single argument.

If you wish SFINAE to eliminate this choice, you need to move the expression that fails the substitution to the function signature.

Related