I've not been able to find a better title, but feel free to modify it if you have the right idea. As it is, it's better than GCC vs clang anyway.
I'm trying to figure out what's wrong in this code:
template <typename... T>
struct S;
template <typename T, typename... U>
struct S<T, U...>: S<U...> {
using S<U...>::f;
template<void(*F)(const T &)>
void f() { }
};
template<>
struct S<> {
void f();
};
template<typename... T>
struct R: S<T...> {
using S<T...>::f;
template<typename U, void(*F)(const U &)>
void g() {
this->template f<F>();
}
};
void h(const double &) { }
int main() {
R<int, double> r;
r.g<double, h>();
}
It compiles with GCC 4.9 (see here), but it doesn't compile with clang 3.8.0 (see here).
Which compiler is right and why?
Moreover, what could I do to see the code compiling with both the compilers?
I believe that clang is correct here and this is a gcc bug. First, let's start with a simplified example:
struct U {
template<int > void foo() { }
};
struct X : U {
using U::foo;
template<void* > void foo() { }
};
int main() {
X{}.foo<1>(); // gcc ok, clang error
}
From [namespace.udecl]:
When a using-declaration brings declarations from a base class into a derived class, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list (8.3.5), cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting). Such hidden or overridden declarations are excluded from the set of declarations introduced by the using-declaration.
U::foo
and X::foo
have the same name, parameter-type-list (none†), cv-qualification (none), and ref qualifier (none). Hence, X::foo
hides U::foo
rather than overloading it and we're definitely passing the wrong type into X::foo
.
Simply overloading on different function pointer types (rather than taking them as template parameters) works fine:
template <typename T, typename... U>
struct S<T, U...> : S<U...> {
using S<U...>::f;
void f(void (*F)(const T&)) { }
};
Or if you really need the function pointers as template arguments, could still overload by wrap them in a tag type:
template <class T>
using fptr = void(*)(const T&);
template <class T, fptr<T> F>
using func_constant = std::integral_constant<fptr<T>, F>;
and propagate that through:
template <typename T, typename... U>
struct S<T, U...>: S<U...> {
using S<U...>::f;
template <fptr<T> F>
void f(func_constant<T, F> ) { }
};
and:
template <class U, fptr<U> F>
void g() {
this->f(func_constant<U, F>{});
}
†The definition of parameter-type-list doesn't mention template parameters.
After @barry - changing the definition of f
in S
specialization to:
template <typename T, typename... U>
struct S<T, U...>: S<U...> {
using S<U...>::f;
template<void(*F)(const T &)>
void f(T *= nullptr) { }
};
Makes also clang working with your code.
Edit:
To make the code even simpler you could change the specializations to:
template <typename T, typename... U>
struct S<T, U...>: S<T>, S<U...> {
};
template<typename T>
struct S<T> {
template<void(*F)(const T &)>
void f() { }
}
And then invoke your f
method in g
using:
S<U>::template f<F>();
Using this option you can go a step further and as @barry suggested use a tag dispatching but in template parameter rather than as a paramter of a function:
template <typename... T>
struct S;
template <typename T>
struct footag { };
template <typename T, typename... U>
struct S<T, U...>: S<footag<T>>, S<U...> {
};
template<typename T>
struct S<T>:S<footag<T>> {
};
template<typename T>
struct S<footag<T>> {
template <void (*F)(const T&)>
void f() { }
};
template<typename V, typename... T>
struct R: S<V, T...> {
template<typename U, void(*F)(const U &)>
void g() {
S<footag<U>>::template f<F>();
}
};
void h(const float &) { }
int main() {
R<int, float, double> r;
r.g<float, h>();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With