13

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?

Barry
  • 286,269
  • 29
  • 621
  • 977
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • It's strange (still testing) and for now i can suggest add flag "variadic-templates" to the question. – Radek Apr 04 '16 at 18:01
  • It appears that ICC also fails to compile this:https://godbolt.org/g/N67e2z – NathanOliver Apr 04 '16 at 18:46
  • Maybe related to [this question](http://stackoverflow.com/questions/8629839/c-candidate-template-ignored-invalid-explicitly-specified-argument-for-templ) or [this question](http://stackoverflow.com/questions/33872039/invalid-explicitly-specified-argument-in-clang-but-successful-compilation-in-gcc)? – callyalater Apr 04 '16 at 19:19
  • @Nathan AFAIK icc13.0 is quite old and does not implements all of C++11 standard, – Radek Apr 04 '16 at 19:19
  • I have found similar [problem with variadic here.](http://stackoverflow.com/questions/4420828/another-bug-in-g-clang-c-templates-are-fun) The template member lookup differs for normal template and variadic template and that might be same for you. – Radek Apr 04 '16 at 19:22
  • I actually think that gcc is correct on this one. – callyalater Apr 04 '16 at 20:06
  • @callyalater Despite what Barry said? Feel free to add your own answer, it would be appreciated. – skypjack Apr 04 '16 at 20:09
  • 1
    @skypjack I'm working on it... – callyalater Apr 04 '16 at 20:11
  • @skypjack see my edit... – W.F. Apr 05 '16 at 19:14

2 Answers2

9

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • But parameter-type-list does mention parameter packs which come from the template declaration. So, doesn't that mean that the templates would be considered in function resolution for overloading vs hiding? – callyalater Apr 04 '16 at 19:36
  • @callyalater But a parameter pack is still a parameter. A template argument isn't. – Barry Apr 04 '16 at 20:22
  • @Barry A kind of tag dispatching, right? I thought the same, but I was wondering if that's the most suitable solution. Thank you for the detailed response (as usual, really appreciated). – skypjack Apr 04 '16 at 20:51
3

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>();
}
W.F.
  • 13,888
  • 2
  • 34
  • 81
  • 1
    Well, `nullptr` instead of `NULL` would be more *C++11*-ish, wouldn't it? – skypjack Apr 04 '16 at 19:56
  • I guess to rely on tag dispatching is a nicer solution. What you are proposing looks more like a mixin based approach, but it is not what I'm looking for (do not misunderstand, I really like the mixin, but this is not the problem for which I'd use them). Does it make sense? – skypjack Apr 05 '16 at 19:19
  • @skypjack You're right but I think its kind of both - mixin and tag dispatching. I do not suggest that it is better solution, just an alternative approach that may be taken and worth to be considered... – W.F. Apr 05 '16 at 19:29
  • Absolutely, it's worth it, that's why I spent a bit of time to give you a feedback. That's my way of saying *thank you*!! :-) – skypjack Apr 05 '16 at 19:34
  • 1
    @skypjack Well, making edit was much as saying *interesting question!* so we're even ;-) – W.F. Apr 05 '16 at 20:00