Search code examples
c++templatesc++17variadic-templatestemplate-meta-programming

How to solve "ambiguous base class" error while deriving from recurring template class?


The following code is failing to compile on both GCC 9.1 and Clang 9.0:

#include <iostream>
#include <string>

template<typename ...> class Foo;

template<typename T>
class Foo<T>{
public:
    void foo() {
        std::cout << "foo (1)\n";
    }
};

template<typename T, typename ... Tail>
class Foo<T, Tail...> : public Foo<Tail...>  {
public:
    void foo() {
        std::cout << "foo (2)\n" ;
    }
};


class Bar : public Foo<std::string, int> {
public:
    template<typename ...Args>
    void test(Foo<Args...> f) {
        f.foo();
    }
};

class Baz : public Foo<int, std::string> {
public:
    template<typename ...Args>
    void test(Foo<Args...> f) {
        f.foo();
    }
};


int main() {
    Bar a;
    Bar b;
    a.test(b);

    Bar c;
    Baz d;
    c.test(d);   
    
    return 0;
}

However, it works fine with latest versions.

Is this code actually valid? Why is it considered ambiguous? Is there any workaround that would work with previous versions of GCC and Clang?


Solution

  • This is a possible workaround for both GCC and Clang: an explicit static_cast to the base class. For convenience I inserted a type alias in the derived base* class, such that you can call test with the same syntax in both cases.

    #include <iostream>
    #include <string>
    
    template<typename ...> class Foo;
    
    template<typename T>
    class Foo<T>{
    public:
        void foo() {
            std::cout << "foo (1)\n";
        }
        using footype = Foo<T>;
    };
    
    template<typename T, typename ... Tail>
    class Foo<T, Tail...> : public Foo<Tail...>  {
    public:
        void foo() {
            std::cout << "foo (2)\n" ;
        }
        using footype = Foo<T, Tail...>;
    };
    
    
    class Bar : public Foo<std::string, int> {
    public:
        template<typename ...Args>
        void test(Foo<Args...> f) {
            f.foo();
        }
    };
    
    class Baz : public Foo<int, std::string> {
    public:
        template<typename ...Args>
        void test(Foo<Args...> f) {
            f.foo();
        }
    };
    
    
    int main() {
        Bar a;
        Bar b;
        a.test(static_cast<decltype(b)::footype>(b));
    
        Bar c;
        Baz d;
        c.test(static_cast<decltype(d)::footype>(d));   
        
        return 0;
    }
    

    Check it on goldbolt with gcc 9.1 and clang 9.

    *Edit after comment.