Is it legal to partially specialise variadic templ

2019-04-19 06:49发布

问题:

Consider the code:

#include <iostream>

template <class... Ts>
struct outer {
   template <class... ITs>
   struct inner {
      static constexpr bool value = false;
   };

   template <class... ITs>
   struct inner<Ts..., ITs...> {   
      static constexpr bool value = true;
   };
};

int main() {
   std::cout << outer<int, float, double>::inner<int, float, double, int>::value << std::endl;
}

The code compiles with clang++ but not with g++ where it produces an error:

temp3.cc:11:11: error: parameter pack argument ‘Ts ...’ must be at the end of the template argument list

struct inner<Ts..., ITs...> {
       ^

As I've already established here partial specialisation of the inner class should be legit.

Edit: For completeness it is worth adding that clang for the above code warns that he might have a problem with deducing ITs parameters yet doing it without any problems...

回答1:

This is a gcc bug. This is a perfectly valid partial specialization:

template <class... ITs>
struct inner<Ts..., ITs...> {   
   static constexpr bool value = true;
};

Deduced template parameter packs must be last, and ITs... satisfies that. But Ts... isn't a pack that needs to be deduced here, it's just a specific parameter pack.

Furthermore, gcc compiles several equivalent formulations:

template <class... Ts>
struct X {
    template <class... Us>
    static void foo(Ts..., Us...) { }
};

int main() {
   X<int>::foo(1, 'c');
}

and:

template <class... Us>
struct A { };

template <class... Ts>
struct X {
    template <class... Us>
    static void foo(A<Ts..., Us...>) { }
};

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

These are equivalently well-formed to your original example.



回答2:

Riding on W.F.'s answer, still keeping the same main as in the original question:

#include <iostream>

template <class... Ts>
struct pack { };

template <class... Ts>
class outer {
   template <class IT>
   struct _inner {
      static constexpr bool value = false;
   };

   template <class... ITs>
   struct _inner<pack<Ts..., ITs...>> {
      static constexpr bool value = true;
   };
public:
   template <class... ITs>
   struct inner {
      static constexpr bool value = _inner<pack<ITs...>>::value;
   };    
};

int main() {
   std::cout << outer<int, float, double>::inner<int, float, double, int>::value
             << std::endl;
}

It still produces a warning in clang, because the specialized version of _inner couldn't deduce ITs... apart from the list of Ts..., ITs... (in struct _inner<pack<Ts..., ITs...>>) -- however the code do not require ITs to be deduced separately from the list of Ts..., ITs..., so this should be ok.

In g++ it compiles without a warning.

Code: http://coliru.stacked-crooked.com/a/ae3b21dd847450b2

(For a solution without a warning also in clang: http://coliru.stacked-crooked.com/a/0c6c643c8ff5809e).



回答3:

Possible simple yet effective workaround inspired on Barry's answer:

#include <iostream>

template <class... Ts>
struct pack { };

template <class... Ts>
struct outer {
   template <class IT>
   struct inner {
      static constexpr bool value = false;
   };

   template <class... ITs>
   struct inner<pack<Ts..., ITs...>> {   
      static constexpr bool value = true;
   };
};

int main() {
   std::cout << outer<int, float, double>::inner<pack<int, float, double, int>>::value << std::endl;
}

(It still produces the warning in clang though)