4

I wonder if anyone knows why the following two pieces of code behave very differently. I can understand why the second one doesn't work, but why does the first one work? At the same place int x = gc.f(); the template should get instantiated so the same error would occur, but why actually there is no error?

a.cpp

#include <iostream>

using namespace std;

template <typename T>
struct A {
    struct B {
    };
};

template <typename T>
struct C {
    typedef A<C<T> > D;

    int f() {
        typename D::B p;
        return 0;
    }
};

C<float> gc;
int x = gc.f();

template <typename T>
struct A<C<T> > {
    struct B {
        B() {
            cout << "B::B()" << endl;
        }

        ~B() {
            cout << "B::~B()" << endl;
        }
    };
};

int main() {
}

output

B::B()
B::~B()

and

a2.cpp

#include <iostream>

using namespace std;

template <typename T>
struct A {
    struct B {
    };
};

struct C {
    typedef A<C> D;

    int f() {
        D::B p;
        return 0;
    }
};

C gc;
int x = gc.f();

template <>
struct A<C> {
    struct B {
        B() {
            cout << "B::B()" << endl;
        }

        ~B() {
            cout << "B::~B()" << endl;
        }
    };
};

int main() {
}

compiler error

a2.cpp:24: error: specialization of ‘A<C>’ after instantiation
a2.cpp:24: error: redefinition of ‘struct A<C>’
a2.cpp:6: error: previous definition of ‘struct A<C>’
Kan Li
  • 8,557
  • 8
  • 53
  • 93

3 Answers3

3

You are actually asking for two different things, although both related to template instantiation.

Why does the first piece of code compile?

The standard states that the actual instantiation of the template is performed after the whole translation unit is processed, which means that the real instantiation of the template will be after all types defined in that translation unit are complete, even if the point of instantiation is different and much earlier in the translation unit. More on this in this other question

Why does the second example not compile?

The problem with the second example is that the standard requires that an specialization of a template must be declared before the first use of that specialization.

§14.7.3p6 (C++03)

If a template, a member template or the member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantia- tion to take place, in every translation unit in which such a use occurs; no diagnostic is required.

Note that there are two different concepts here. The point of instantiation refers to where in the code the instantiation takes place, not when it is instantiated. In your example the point of instantiation is the expression C<float> gc;, while the when as in all other cases is after the whole translation unit is processed.

Community
  • 1
  • 1
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • +1 I think you are basically right, but there is some subtlety here that could probably use more explanation. For example, in the first example, if C had a member of type D::B and there was a full specialization of A >, then there would still be an instantiation error. – Vaughn Cato Feb 29 '12 at 03:39
  • @VaughnCato if there was a full specialization of `A >` before or after the point of instantiation? If it is after then we are back in case 2. If it is *before* the point of instantiation, I don't think (not 100% sure, would have to test) it should fail to compile. – David Rodríguez - dribeas Feb 29 '12 at 12:50
  • I was talking about doing a full specialization of A > instead of a partial specialization at the same place as case 1. It appears that full specialization of an already instantiated class causes an error, while partial specialization doesn't (haven't found that in the standard yet), so that is another reason why case 1 works. Now, if you only switch the partial specialization to a full specialization, case 1 will still work, apparently due to what you are talking about. But what I'm seeing is that it is due to the instantiation being inside a member function body. – Vaughn Cato Feb 29 '12 at 15:09
  • @VaughnCato: The quote that you are looking for is 14.7.3p6 (in the answer). An *explicit specialization* is what you are referring to as *full specialization*. (14.7.3p1 *An explicit specialization [...] can be declared by a declaration introduced by `template<>`*) 14.7.3p6 states that it has to be declared *before* (above) the first use of the specialization. – David Rodríguez - dribeas Feb 29 '12 at 16:57
  • Thanks, I didn't realize that "explicit" was the proper term. Seems an odd term, since "explicit" is more like the opposite of "implicit" rather than "partial". In any case, explicit specialization is handled differently from partial specialization in some ways, so that is part of the reason why case 1 works. – Vaughn Cato Feb 29 '12 at 18:21
  • @VaughnCato: My believe is that this is just an issue with our understanding of what *specialization* means in general compared with the precise meaning in the standard. My *believe*, I don't have the energy to track this down in the standard now, is that *specialization* is the application of the template for a fixed concrete set of parameters. In this case a *full* specialization is explicitly fixing all those parameters, while a *partial* specialization provides the way for the compiler to *implicitly* specialize the template for a subset of the arguments. – David Rodríguez - dribeas Feb 29 '12 at 19:42
2

You just have to remove the unnecessary typename qualifier and rearrange things a bit. As 2nd the error message says, you have specialized A<C> after it's already been instantiated in C. To fix this you can move the specialization of A<C> up to before the definition of C and then forward declare C to get rid of the undeclared identifier error.

Here's the fixed code:

#include <iostream>

using namespace std;

template <typename T>
struct A {
    struct B {
    };
};

struct C; // Forward declaration of C

template <>
struct A<C> {
    struct B {
        B() {
            cout << "B::B()" << endl;
        }

        ~B() {
            cout << "B::~B()" << endl;
        }
    };
};

struct C {
    typedef A<C> D;

    int f() {
        D::B p;
        return 0;
    }
};

C gc;
int x = gc.f();


int main() {
}
David Brown
  • 13,336
  • 4
  • 38
  • 55
1

Because typename is only to be used when indicating types from within a template.

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • Seems odd that it's actually prohibited when not required. – Lightness Races in Orbit Feb 29 '12 at 00:43
  • Compiling with gcc C++0x doesn't give the typename error ([link](http://ideone.com/vieT7).) However, older versions [do](http://ideone.com/FeDgR). So, I think the rules changed between c++03 and c++11. – Jesse Good Feb 29 '12 at 00:47
  • 3
    I think I'd prefer that `typename` not be allowed in places it's not required. Otherwise people may just liberally sprinkle it around when they have a problem instead of figuring out where it's actually needed. – bames53 Feb 29 '12 at 00:57
  • Found the relevant quote: `As is the case with the typename prefix, the template prefix is allowed in cases where it is not strictly necessary; i.e., when the nested-name-specifier or the expression on the left of the -> or . is not dependent on a template-parameter, or the use does not appear in the scope of a template.` 14.2.5 – Jesse Good Feb 29 '12 at 00:57
  • Sorry I was actually trying to ask the problem about the specialization of ‘A’ after instantiation – Kan Li Feb 29 '12 at 02:57