18

While trying to implement a few things relying on variadic templates, I stumbled accross something I cannot explain. I boiled down the problem to the following code snippet:

template <typename ... Args>
struct A {};

template <template <typename...> class Z, typename T>
struct test;

template <template <typename...> class Z, typename T>
struct test<Z, Z<T>> {
    static void foo() {
        std::cout << "I'm more specialized than the variadic spec, hehe!" << std::endl;
    }
};

template <template <typename...> class Z, typename T, typename ... Args>
struct test<Z, Z<T, Args...>> {
    static void foo() {
        std::cout << "I'm variadic!" << std::endl;
    }
};

int main() {
    test<A, A<int>>::foo();
}

Under gcc, it produces an error because it considers both specializations to be equally specialized when trying to instantiate test<A, A<int>>:

main.cpp: In function 'int main()':

main.cpp:25:24: error: ambiguous template instantiation for 'struct test<A, A<int> >'

         test<A, A<int>>::foo();

                        ^~

main.cpp:11:12: note: candidates are: template<template<class ...> class Z, class T> struct test<Z, Z<T> > [with Z = A; T = int]

     struct test<Z, Z<T>> {

            ^~~~~~~~~~~~~

main.cpp:18:12: note:                 template<template<class ...> class Z, class T, class ... Args> struct test<Z, Z<T, Args ...> > [with Z = A; T = int; Args = {}]

     struct test<Z, Z<T, Args...>> {

However, clang deems the first specialization "more specialized" (through partial ordering: see next section) as it compiles fine and prints:

I'm more specialized than the variadic spec, hehe!

A live demo can be found on Coliru. I also tried using gcc's HEAD version and got the same errors.

My question here is: since these two well-known compilers behave differently, which one is right and is this piece of code correct C++?


Standard interpretation (C++14 current draft)

From the sections §14.5.5.1 and $14.5.5.2 of the C++14 standard draft, partial ordering is triggered to determine which specialization should be chosen:

(1.2) — If more than one matching specialization is found, the partial order rules (14.5.5.2) are used to determine whether one of the specializations is more specialized than the others. If none of the specializations is more specialized than all of the other matching specializations, then the use of the class template is ambiguous and the program is ill-formed.

Now according to §14.5.5.2, the class template specializations are transformed into function templates through this procedure:

For two class template partial specializations, the first is more specialized than the second if, given the following rewrite to two function templates, the first function template is more specialized than the second according to the ordering rules for function templates (14.5.6.2):

(1.1) — the first function template has the same template parameters as the first partial specialization and has a single function parameter whose type is a class template specialization with the template arguments of the first partial specialization, and

(1.2) — the second function template has the same template parameters as the second partial specialization and has a single function parameter whose type is a class template specialization with the template arguments of the second partial specialization.

Therefore, I tried to reproduce the issue with the function template overloads that the transformation described above should generate:

template <typename T>
void foo(T const&) {
    std::cout << "Generic template\n";
}

template <template <typename ...> class Z, typename T>
void foo(Z<T> const&) {
    std::cout << "Z<T>: most specialized overload for foo\n";
}

template <template <typename ...> class Z, typename T, typename ... Args>
void foo(Z<T, Args...> const&) {
    std::cout << "Z<T, Args...>: variadic overload\n";
}

Now trying to use it like this:

template <typename ... Args>
struct A {};

int main() {
    A<int> a;
    foo(a);
}

yields a compilation error [ambiguous call] in both clang and gcc: live demo. I expected clang would at least have a behavior consistent with the class template case.

Then, this is my interpretation of the standard (which I seem to share with @Danh), so at this point we need a to confirm this.

Note: I browsed a bit LLVM's bug tracker and could not find a ticket for the behavior observed on function templates overloads in this question.

Rerito
  • 5,886
  • 21
  • 47
  • 1
    Doesn't variadic template argument mean `0 to N` parameters ? In that case g++ seems to be correct. – Arunmu Nov 30 '16 at 15:35
  • @Arunmu The template in itself isn't variadic. I rely on variadic templates for one of the specializations, which is usually totally [fine](http://coliru.stacked-crooked.com/a/13e143ee16ced268) – Rerito Nov 30 '16 at 15:38
  • 2
    Whatever it is, it's not partial ordering--that only applies to function templates, not class templates. – Jerry Coffin Nov 30 '16 at 15:41
  • @Rerito I was saying `Args...` could very well be an empty set, making both your specializations same for a single template parameter, atleast thats what my guess is. – Arunmu Nov 30 '16 at 15:43
  • @JerryCoffin That's not what I [read](http://en.cppreference.com/w/cpp/language/partial_specialization). – Rerito Nov 30 '16 at 15:44
  • @Arunmu which again would be [totally fine with gcc](http://coliru.stacked-crooked.com/a/2405a16c1a8bba4f) if it weren't embedded into a specialization of `Z`. That's definitely peculiar, isn't it? :) – Rerito Nov 30 '16 at 15:46
  • @Rerito Yup, it's wierd. – Arunmu Nov 30 '16 at 15:50
  • For what it's worth, VC++ agrees with g++. – Jerry Coffin Nov 30 '16 at 16:04
  • 2
    By removing `Z`, gcc/clang agree on more specialized [Demo](http://coliru.stacked-crooked.com/a/a13b9b9b911639bc). So, I would say bug of gcc. – Jarod42 Nov 30 '16 at 16:21
  • I think g++ is right, as `Args...` could expand to `0` or more arguments. Anyway, using one more template parameter `U` in the specialization as `test>` should fix the issue. – Nawaz Nov 30 '16 at 16:21
  • @Nawaz: So why the others demos ([totally fine with gcc](http://coliru.stacked-crooked.com/a/2405a16c1a8bba4f), [Demo](http://coliru.stacked-crooked.com/a/a13b9b9b911639bc)) works ? – Jarod42 Nov 30 '16 at 16:25
  • @Jarod42: Now only dissecting the spec can save us from this confusion. The question is, *who* will do that? – Nawaz Nov 30 '16 at 16:28
  • @Nawaz I just checked 14.5.5.1 and 14.5.5.2 of the standard. They seem to agree with clang here – Rerito Nov 30 '16 at 16:31
  • @Rerito: I think, you should quote that along with *your* own understanding. – Nawaz Nov 30 '16 at 16:35
  • @Nawaz Will do as soon as I get back on a computer – Rerito Nov 30 '16 at 16:38

1 Answers1

2

From temp.class.order:

For two class template partial specializations, the first is more specialized than the second if, given the following rewrite to two function templates, the first function template is more specialized than the second according to the ordering rules for function templates ([temp.func.order]):

  • Each of the two function templates has the same template parameters as the corresponding partial specialization.

  • Each function template has a single function parameter whose type is a class template specialization where the template arguments are the corresponding template parameters from the function template for each template argument in the template-argument-list of the simple-template-id of the partial specialization.

The order of:

template <template <typename...> class Z, typename T>
struct test<Z, Z<T>> {
    static void foo() {
        std::cout << "I'm more specialized than the variadic spec, hehe!" << std::endl;
    }
};

template <template <typename...> class Z, typename T, typename ... Args>
struct test<Z, Z<T, Args...>> {
    static void foo() {
        std::cout << "I'm variadic!" << std::endl;
    }
};

depends on the order of:

template <template <typename...> class Z, typename T>
void bar(test<Z, Z<T>>); // #1
template <template <typename...> class Z, typename T, typename ... Args>
void bar(test<Z, Z<T, Args...>>); // #2

From [temp.func.order]:

Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the function type. The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process.

To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs ([temp.variadic]) thereof) synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template.

Using the transformed function template's function type, perform type deduction against the other template as described in [temp.deduct.partial].

By those paragraph, for any function transformed from any synthesized template Z0 and type T0, which can form #1, we can do type deduction with #2. But functions transformed from #2 with fictitious template Z2 with any type T2 and any non-empty set of Args2 can't be deduced from #1. #1 is obviously more specialized than #2.

clang++ is right in this case.


Actually, this one and this one are failed to compile (because of ambiguous) in both g++ and clang. It seems like both compilers have hard time with template template parameters. (The latter one is clearly ordered because its order is the same of no function call).

Community
  • 1
  • 1
Danh
  • 5,916
  • 7
  • 30
  • 45
  • Interesting. The second 'this one' did compile successfully with Microsoft v140. – lakeweb Dec 01 '16 at 01:15
  • I share the same interpretation of the standard (I took a look to confirm this yesterday). However, I tried to reproduce the behavior with function templates instead and ended up with failures in both clang and gcc, [just like you](http://coliru.stacked-crooked.com/a/8f4232a317c6f7be) – Rerito Dec 01 '16 at 08:48
  • @Rerito the first time I interpreted the standard, I thought g++ was right (you can see it in the post history) because both g++ and clang failed to compile the function templates. It took me a lot of time to recognise that both of them was wrong when compile another simple case (my last example) – Danh Dec 01 '16 at 08:51
  • 1
    @Danh exactly. This is troubling because when transforming the partial class template specializations into function template overloads, clang should end up with the same function templates we implemented... However it fails to deduce the good overload with function templates?! This is also reproducible using clang with no template template parameter: [see this](http://coliru.stacked-crooked.com/a/2323dd7d0fa9dde3). But in that last example, g++ gets it right... – Rerito Dec 01 '16 at 08:55