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.