13

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...

Community
  • 1
  • 1
W.F.
  • 13,888
  • 2
  • 34
  • 81
  • I don't know the rules exactly, but when I get an error with a dependent type, adding `typename` or `template` before it sometimes helps. Try `struct inner – Daniel Jun 11 '16 at 19:18
  • after adding `typename` I get `temp3.cc:11:39: error: template argument 1 is invalid` – W.F. Jun 11 '16 at 19:20
  • It is to be noted that the requested scenario can still be implemented with some additional template metaprogramming... http://coliru.stacked-crooked.com/a/0c6c643c8ff5809e (yes, I know that was not the question but the challenge of implementing it was unavoidable...). – Amir Kirsh Jun 20 '16 at 01:03

3 Answers3

8

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • I suspected that but wasn't sure if isn't there some special rule for the context of specialization inside a template class. Is it a known bug? – W.F. Jun 11 '16 at 21:02
  • 4
    I do agree that it makes sense for the partial specialization to be valid, but I'm not sure that the Standard as written allows it. [14.5.5p8.5] says *If an argument is a pack expansion (14.5.3), it shall be the last argument in the template argument list*. It's a specific requirement on partial specializations, so it doesn't apply to your examples. This looks more like a Standard issue than a compiler bug to me. – bogdan Jun 11 '16 at 22:32
  • You should submit bug report to `gcc`. Have you done? – Destructor Jun 12 '16 at 08:09
  • @bogdan I don't think `Ts...` counts as a pack expansion - it will have already been expanded by the time we consider the partial specialization, no? – Barry Jun 13 '16 at 02:12
  • 1
    I've seen [the discussion](https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/pMaEyvcC9Y8), but I'm not convinced. I'll reply to that thread. – bogdan Jun 13 '16 at 11:50
  • 1
    I don't think we're going to get any more information out of that thread. My conclusion based on the discussion is that the intent is for this to work but a strict interpretation of the wording disallows it. GCC, MSVC and EDG take the wording literally, while Clang implements the intended behaviour; it's hard to place the blame on any of them without clarifying the wording first. I heard there's an ISO rule specifying that the OP on std-discussion is also the one who sends the issue report :-). – bogdan Jun 28 '16 at 16:44
1

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).

Amir Kirsh
  • 12,564
  • 41
  • 74
0

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)

W.F.
  • 13,888
  • 2
  • 34
  • 81