3

I have some compiler behavior - different on VisualC++ and g++ - that I don't understand (for either compiler). I will describe it in English but maybe it would be easier just to look at the code below.

It has to do with a class template that has a member class template and a member function template. I am trying to simultaneously

  1. Explicitly specialize the outer class template (leaving the member templates unspecialized)
  2. Explicitly specialize the member templates within the explicit specialization of the outer class template.

I find that, with both compilers, everything compiles fine (and instantiations are as expected) if I do either #1 (explicitly specialize outer class template) or #2 (explicitly specialize member templates).

But if I try to do both #1 and #2 simultaneously (declared in the correct order I believe), I find that

  • With g++, I can do this for the member class template, but get a compiler error for the member template class declaration.
  • For VC++, I get compiler errors for both member templates.

Here is the outer class template primary definition. It is the same in all of the cases below:

// Class template with a member class template and a member function template
template <int I>
struct OuterClass
{
    template <int J> struct InnerClass {};
    template <int J> static void InnerFunc() {}
};

Here is doing #1 (explicitly specialize outer class template) only. This compiles fine and instantiations are as expected.

// Explicit specialization of outer class template,
// leaving member templates unspecialized.
template <>
struct OuterClass<1>
{
    template <int J> struct InnerClass {};
    template <int J> static void InnerFunc() {}
};

Here is doing #2 (explicitly specialize member templates) only. This compiles fine and instantiations are as expected.

// Explicit specialization of inner templates for
// an explicit specialization of outer class template
template <> template <> struct OuterClass<1>::InnerClass<1> {};
template <> template <> void OuterClass<1>::InnerFunc<1>() {}

Here is trying to do both #1 and #2 simultaneously - just pasting the two previous code snippets together:

// Explicit specialization of outer class template,
// leaving member templates unspecialized.
template <>
struct OuterClass<1>
{
    template <int J> struct InnerClass {};
    template <int J> static void InnerFunc() {}
};

// Explicit specialization of inner templates for
// an explicit specialization of outer class template
template <> template <> struct OuterClass<1>::InnerClass<1> {};  // Line A
template <> template <> void OuterClass<1>::InnerFunc<1>() {}    // Line B

g++ compiles "Line A" fine (and instantiations are as expected). But g++ gives a compiler error for Line B: "too many template-parameter-lists".

VC++ gives compiler errors for both "Line A" and "Line B" (too messy and numerous to recount here).

Again, both "Line A" and "Line B" compile fine, for both compilers, when they do not appear after the explicit specialization of the outer class template.

In my understanding, everything should compile fine. So who is right - me, g++, or VC++? And more importantly, why?

Please understand this is not a "How do I accomplish X" question, this is a "I want to fully understand C++" question. If you take the time to read through this and think about it you have my thanks...I hope I boiled it down as much as possible.

David Stone
  • 1,132
  • 7
  • 18
  • 1
    I'd make this an answer if I were sure. But I've never seen a double `template<> template<>` used before. With a single `template<>` it compiles fine with Microsoft ms140. – lakeweb Oct 19 '18 at 21:28
  • @lakeweb in gcc and clang as well. There are however examples of "template<> template<>" in pt 14-15 of the section "Templates->Template instantiation and specialization->Explicit specialization" in the C++ drafts (its number varies between versions) – max630 Oct 20 '18 at 07:50
  • Hi @max360 thanks. So I've read through [this](https://en.cppreference.com/w/cpp/language/template_specialization) and it makes more sense. I can't find the rule but seem to know that you can't duplicate a specialization. So using a double `template<>` is a request for a new specialization and `struct OuterClass<1>` has already been done. I'm I getting close? – lakeweb Oct 20 '18 at 16:45
  • @lakeweb Thanks for your response, you are right a single template<> compiles with MSVC with #1 and #2 present simultaneously. That got me started in the right direction, I think I figured this out - please see my self-answer below. As max630 points out, there are times when multiple appearances of "template<>" is legal (and required) - see also also the code example in my answer and the examples of the bottom of en.cppreference.com/w/cpp/language/template_specialization. – David Stone Oct 24 '18 at 19:35

2 Answers2

2

I spent a ridiculous amount of time on this issue and I guess I got it figured out - where by "figured out" I mean I know exactly what will and will not compile on both the compilers I've tried (MSVC and g++), and what syntax to use for each compiler. This is all ugly but it is predictable and repeatable - I have a lot of example code not shown here from which I deduced the results. To be clear and avoid frustration on my part, the description here is not my theory it is observed behavior of the compilers across hunderds of example cases.

At a high level:

  • There is a bug (?) in MSVC that prevents compilation, in certain well-defined cases, of a complete explicit specialization of a function template nested in a class template.
  • Both compilers can always compile explicit specializations of class templates nested in a another class template. But, the rule for how many times "template<>" must appear is different for MSVC vs. g++. So for portable code you have to use conditional compilation in some cases.

The detailed description I give here is probably too brief to be understood by the casual reader, but I'll try anyway.

  • In general, when declaring/defining a specialization of a member of a class template (possibly in a deep chain of nested class templates), the number of times "template<>" must appear is equal to the number of "specialization hops" from the closest class template specialization that "covers" the specialization being declared, to the specialization being declared.
    • Call this the "Last Hop Rule".
    • This applies for all kinds of specializable members (class/class template, function/function template, variable/variable template, and enum), on both compilers, with one exception (see below)
  • For declaring/defining specializations of a class template nested in another class template (possibly in a deep chain of nested class templates)
    • For MSVC: The number of times "template<>" must appear is the "Last Hop Rule"
    • For g++: This is the one case where the "Las Hop Rule" doesn't apply. (For whatever reason, I guess a bug in g++?). In this case, the number of times "template<>" must appear is equal to the "depth" of the specialization being declared, minus the total number of template class specializations that cover the specialization being declared.
  • For declaring/defining specializations of a function template nested in a class template (possibly in a deep chain of nested class templates)
    • For MSVC: This won't compile in some cases. I deduced the conditions will compile and when it will not compile, but it is too complicated to describe here. I guess there is a bug in MSVC - g++ always works. When it does work, the number of times "template<>" appears in the "Last Hop Rule" (for both compilers).
    • For g++: This always works. The number of times "template<>" appears in the "Last Hop Rule".

Here is some example code, showing how many times "template<>" must appear when defining a nested class template with a particular specialization chain. This compiles on both MSVC and g++ (using conditional compilation). This code does not involve nested function templates.

#include <boost\predef.h>  // For conditional compilation

// Nested class templates, 3 deep.
template <int I1> struct T1
{
    template <int I2> struct T2
    {
        template <int I3> struct T3 {};
    };
};

// Specialization of the third level of class template.
// "template<>" appears three times here for both MSVC and g++ -
// in this case the rules for both compilers both yield 3.
// Note this class template specialization nests another 2 levels of class templates.
template <> template <> template<> struct T1<1>::T2<1>::T3<1>
{
    template <int I4> struct T4 
    {
        template <int I5> struct T5 {};
    };
};

// Specialize the class template contained in the class template specialization above.
// In this case, the number of times "template<>" must appear differs between MSVC and g++,
// so conditional compilation is used.
#if BOOST_COMP_GNUC
// According to the rule described for g++, "template<>" must appear 4 times: 
// (Overall specialization level of 5) - (1 covering specialization which is T1<1>::T2<1>::T3<1>) = 4
template <> template<> template<> template<> struct T1<1>::T2<1>::T3<1>::T4<1>::T5<1> 
#elif BOOST_COMP_MSVC
// MSVC uses the last hop specialization rule, so "template<>" must appear 2 times -
// because the closest covering specialization, T1<1>::T2<1>::T3<1>, is two hops back.
template <> template<> struct T1<1>::T2<1>::T3<1>::T4<1>::T5<1>    
#else
#error Unsupported compiler!
#endif
{ 
    //...
}
David Stone
  • 1,132
  • 7
  • 18
  • just found [this](https://stackoverflow.com/questions/52664184/why-does-explicit-template-instantiation-not-break-odr). So why gcc is not complaining. Best, Dan. – lakeweb Nov 10 '18 at 01:36
1

The issue here (as it was in another question of yours) is that, once an explicit specialization has been declared for (the entirety of) a class template, that specialization is syntactically an ordinary class (albeit with a name that includes punctuation):

template<int> struct A {
  void f();
};
template<int I> void A<I>::f() {}

// A specialization of only a member does use template<>:
template<> void A<0>::f() {/*...*/}

template<> struct A<1> {
  void g();
};
// A definition of a member of a specialization doesn't use template<>:
void A<1>::g() {}

Applying the same logic to member templates produces

template<int> struct A {
  template<class> void f();
  template<> void f<void>() {}
};
template<int I> template<class T> void A<I>::f() {}

template<> template<class T> void A<0>::f() {/*...*/}
template<> template<> void A<-1>::f<short>() {/*...*/}
// template<int I> template<> void A<I>::f<int>() {/*...*/}

template<> struct A<1> {
  template<class> void g();
};
template<class T> void A<1>::g() {}
template<> void A<1>::g<char>() {/*...*/}

The commented-out possibility doesn't work: you're not allowed to specialize a member template of a (non-specialized) class template outside that class template, although you can do so inside the template (though GCC and ICC don't implement this yet) or for a member template of a complete class template specialization.

Examples like these are of course a bit more counterintuitive if, in the complete-specialization case, the members happen to have the same names. They're also a bit more confusing in the presence of compiler bugs!

Davis Herring
  • 36,443
  • 4
  • 48
  • 76