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.
- Godbolt GCC 9.1 -> NOK
- Godbolt Clang 9.0 -> NOK
- Godbolt GCC 12.1 -> OK
- Godbolt Clang 12.0 -> OK
Is this code actually valid? Why is it considered ambiguous? Is there any workaround that would work with previous versions of GCC and Clang?
CodePudding user response:
Th code is valid and obviously not ambiguous enough to fail when compiling with the latest gcc compiler. You can use static_cast<> to be more explicit when using the older compiler.
As in:
#include <iostream>
#include <string>
template<typename ...> class Foo;
template<typename T>
class Foo<T>{
public:
void foo() const {
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(static_cast<Foo<std::string, int>>(b));
Bar c;
Baz d;
c.test(static_cast<Foo<int, std::string>>(d));
return 0;
}
You'll find the code here: https://godbolt.org/z/YjPqaKdbj
CodePudding user response:
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 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";
}
};
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();
}
using baseclass = Foo<std::string, int>;
};
class Baz : public Foo<int, std::string> {
public:
template<typename ...Args>
void test(Foo<Args...> f) {
f.foo();
}
using baseclass = Foo<int, std::string>;
};
int main() {
Bar a;
Bar b;
a.test(static_cast<decltype(b)::baseclass>(b));
Bar c;
Baz d;
c.test(static_cast<decltype(b)::baseclass>(b));
return 0;
}