Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 379
skypjack Avatar asked Apr 04 '16 17:04

skypjack


2 Answers

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.

like image 150
Barry Avatar answered Oct 19 '22 09:10

Barry


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>();
}
like image 3
W.F. Avatar answered Oct 19 '22 08:10

W.F.