Шаблоны Variadic и указатели функций: какой компилятор прав?
Я не смог найти лучший заголовок, но не стесняйтесь его модифицировать, если у вас есть правильная идея. Как бы то ни было, это лучше, чем GCC и clang.
Я пытаюсь выяснить, что не так в этом коде:
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>();
}
Он компилируется с GCC 4.9 (см. здесь), но он не компилируется с clang 3.8.0 (см. здесь).
Какой компилятор прав и почему?
Более того, что я мог сделать, чтобы увидеть компиляцию кода вместе с компиляторами?
Ответы
Ответ 1
Я считаю, что clang здесь верен, и это ошибка gcc. Сначала начнем с упрощенного примера:
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
}
От [namespace.udecl]:
Когда декларация using приносит объявления из базового класса в производный класс, функции-члены и шаблоны функций-членов в переопределении производного класса и/или скрыть функции-члены и функцию-член шаблоны с тем же именем, список параметров (8.3.5), cv-qualification и ref-qualifier (если есть) в базовый класс (а не конфликтующий). Такие скрытые или переопределенные объявления исключаются из набора объявления, введенные с помощью декларации использования.
U::foo
и X::foo
имеют одно и то же имя, список параметров-параметров (none & dagger;), cv-qualification (none) и ref qualifier (none). Следовательно, X::foo
скрывает U::foo
, а не перегружает его, и мы определенно передаем неправильный тип в X::foo
.
Просто перегрузка на разных типах указателей функций (а не на их использование в качестве параметров шаблона) работает нормально:
template <typename T, typename... U>
struct S<T, U...> : S<U...> {
using S<U...>::f;
void f(void (*F)(const T&)) { }
};
Или, если вам действительно нужны указатели на функции в качестве аргументов шаблона, все равно можно перегрузить их, поместив их в тип тега:
template <class T>
using fptr = void(*)(const T&);
template <class T, fptr<T> F>
using func_constant = std::integral_constant<fptr<T>, F>;
и распространить это через:
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> ) { }
};
и
template <class U, fptr<U> F>
void g() {
this->f(func_constant<U, F>{});
}
& dagger; В определении параметра-типа-списка не упоминаются параметры шаблона.
Ответ 2
После @barry - изменение определения f
в S
специализации на:
template <typename T, typename... U>
struct S<T, U...>: S<U...> {
using S<U...>::f;
template<void(*F)(const T &)>
void f(T *= nullptr) { }
};
Делает также работу с вашим кодом.
Edit:
Чтобы сделать код еще проще, вы могли бы изменить специализации на:
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() { }
}
И затем вызовите ваш метод f
в g
, используя:
S<U>::template f<F>();
С помощью этой опции вы можете пойти дальше и, как @barry, рекомендуется использовать диспетчер тегов, но в параметре шаблона, а не как параметр функции:
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>();
}