Подтвердить что ты не робот

Шаблоны 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 (см. здесь).

Какой компилятор прав и почему?
Более того, что я мог сделать, чтобы увидеть компиляцию кода вместе с компиляторами?

4b9b3361

Ответ 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>();
}