可变参数模板和函数指针:哪个编译器是正确的?

Variadic templates and functions' pointers: what compiler is right?

我一直找不到更好的标题,但如果您有正确的想法,请随时修改它。事实上,无论如何,它比 GCC vs 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 编译(参见 here), but it doesn't compile with clang 3.8.0 (see here)。

哪个编译器是正确的,为什么?
此外,我该怎么做才能看到用两个编译器编译的代码?

我相信这里的 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]

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::fooX::foo同名,parameter-type-list(none),cv-qualification(none) 和 ref 限定符 (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>{});
}

parameter-type-list的定义没有提到模板参数。

@barry 之后 - 将 S 专业化中 f 的定义更改为:

template <typename T, typename... U>
struct S<T, U...>: S<U...> {
    using S<U...>::f;


    template<void(*F)(const T &)>
    void f(T *= nullptr) { }
};

使用您的代码也能发出 clang 声。

编辑:

为了使代码更简单,您可以将特化更改为:

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() { }
}

然后在 g 中调用您的 f 方法,使用:

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>();
}