Ambiguous base class when inheriting from template class with auto parameter

Viewed 85

Given the code

#include <type_traits>

template <auto I, class T>
struct C {};

template <auto I, class T>
void func(C<I, T> c)  {}

struct C2 : C<0, int>, C<0u, long> {};

int main() {
    static_assert(!std::is_same<C<0, int>, C<0u, long>>::value);

    C2 c2;
    func<0>(c2);  // Error

    return 0;
}

I get the error (from GCC):

<source>:15:15: note:   'C<0, T>' is an ambiguous base class of 'C2'
   15 |     func<0>(c2);
      |               ^

How come a class template (C<I, T>) can be an ambiguous base class when the instantiations (C<0, int> and C<0u, long>) are clearly distinct types?

Edit: I forgot the partial specialization.

2 Answers

The error message could be slightly better. But the issue is in trying to do overload resolution. Two possible overloads can theoretically be synthesized from C2:

void func(C<0, int> c)  {}
void func(C<0u, long> c)  {}

That is because an argument of a class type can be matched against a template that accepts one of its bases. And template argument deduction is capable of deducing the template arguments of the base class.

But here's the kicker: the compiler is only allowed to synthesize one overload from the template, so which base class should it use to synthesize with? That's ambiguous, and hence your error happens. The compiler doesn't know which base class it should use to do template argument deduction for the C specialization. In that sense, those bases are ambiguous.


Your edit does not change much. You specify 0, but that can still match the 0u argument. For the purpose of matching a non-type template parameter, the corresponding template argument must be a converted constant expression of the parameter type. 0 is a constant expression that can be converted to the 0u constant expression and vice-versa. So specifying 0 or even 0u as an explicit argument is not going to resolve the ambiguity.

Specifying int or long, is going to resolve the ambiguity however. So we can rewrite:

template <class T, auto I>
void func(C<I, T> )  {}

and then call

func<int>(c2); 

How come a class template (C<I, T>) can be an ambiguous base class when the instantiations (C<0, int> and C<0u, long>) are clearly distinct types?

The fact that they are distinct types is the problem. C2 inherits from both, and the template func accepts both base classes - which one shall the compiler choose?

You can be explicit about which part of c2 shall be taken when passing it to func, which fixes the issue:

func(static_cast<C<0, int>>(c2));  // Works fine now
Related