4

g++ 3.4.5 accepts this code:

template <typename T> struct A
{
    static const char* const str;
};

struct B {};

typedef A<B> C;

template<> const char* const C::str = "B";
// Equivalent to following?
// template<> const char* const A<B>::str = "B";

But I'm not sure it's actually legal C++03. In particular,

[14.7p3] In an explicit specialization declaration for a class template, a member of a class template or a class member template, the name of the class that is explicitly specialized shall be a template-id.

Does this requirement say that the non-typedef version must be used at the end of this example? Or have I misinterpreted something?

Edit: Further evidence: Defect Report 403 suggests that it is incorrect to say a type (in that context, the type of an argument of a function call expression) is a template-id because template-id has a syntactic meaning, not a semantic one. Later drafts of the Standard have used "class template specialization" instead of "template-id" in 3.4.2.

This supports the argument that although A<B> and C represent the same type (and have identical or nearly identical semantic meaning), A<B> is a template-id and C is not, because the term template-id refers to the syntactic content as a sequence of tokens rather than the meaning of those tokens.

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • Not writing as an answer as I do not have time to verify it against the standard, but my gut feeling tells me that a `typedef` generate an `alias` to the type, not a new type. As such it should be possible to use it in any place where the original type is used. Note that this seems to also be accepted by g++: `struct A { void foo(); }; typedef A B; void B::foo() {} int main() { A a; a.foo(); }` – David Rodríguez - dribeas Dec 16 '10 at 19:05
  • 1
    @David: re "any place", one exception is to define a constructor or destructor, where the original class name has to be used. and there is a funny exception in the other direction, where you can't write `(42).~int()` but you can write `(42).~Integer()` when `Integer` is a typedef for `int`. This has to do with what belongs at syntactic level or not. And I guess the crux of the OP's question has to do with a syntactic level distinction. – Cheers and hth. - Alf Dec 16 '10 at 19:25
  • I would point out that GCC 3.4.5 is pretty old, and accepts rather a lot of things that are not legal C++. And fails to accept some things that are legal, too. But Alf's comment about Comeau online is rather stronger evidence, as Comeau's compiler is quite good. – Brooks Moses Dec 16 '10 at 20:09
  • @Alf P. Steinbach: Thanks I did not know that. Every day you have to learn something new... now I can go back to sleep. – David Rodríguez - dribeas Dec 17 '10 at 11:47

2 Answers2

4

I think this is strictly ill-formed (at least according to C++ '03).

My logic is that although a typedef (7.1.3/1):

... is thus a synonym for another type.

The standard still has words to explictily allow for a typedef to be used where a class-name is required (7.1.3/4):

A typedef-name that names a class is a class-name

There are no such words for template-id.

Richard Corden
  • 21,389
  • 8
  • 58
  • 85
  • I wouldn't agree with you. 7.1.3/4 is intended not to explicitly allow to use typedef-name as a class-name, but explicitly restrict typedef-name usage as a class-name in special cases. At the same time 7.1.3/1 explicitly specifies that typedef-name is a synonym and may be used everywhere if it is not restricted explicitly. – Stas Dec 16 '10 at 20:12
  • A `template-id` is a `class-name`, see [gram.class]. Of course, it's logical to say that a `class-name` is not a `template-id`, but in this situation, I think that would go against the spirit of the definition of a `typedef-name`. – Steve M Dec 16 '10 at 20:16
  • @Stas: If that were the case, then why didn't 7.1.3/4 just start with: "If a typedef-name is used following..."? – Richard Corden Dec 16 '10 at 20:17
  • @Steve M: I agree re "spirit of the typedef-name". This kind of thing is probably harder for compilers to catch and warn about than to just accept the code and keep going. – Richard Corden Dec 16 '10 at 20:24
  • @Richard Corden: I don't know why they use such wording. Anyway, I see two ways of specifying something: "permit all and prohibit only special cases" or "prohibit all and permit only special cases". I guess it is obvious that they have selected the first way. I think, you would agree it is almost impossible to prohibit all and explicitly describe every permitted case for typedefs. – Stas Dec 16 '10 at 20:25
  • @Steve M: See my answer to Richard. I hope it helps. – Stas Dec 16 '10 at 20:31
  • @Stas: The key here is the use of the grammar terms 'typedef-name', 'class-name' and 'template-id'. There is very big difference between a class name (not in italics) vs a 'class-name' (in italics) in the standard, and it is for that reason that we have to allow 'typedef-name' to be used where 'class-name' can be used. – Richard Corden Dec 16 '10 at 20:35
  • @Richard Corden: Read 12.1/3 "A typedef-name that names a class is a class-name (7.1.3); however, a typedef-name that names a class shall not be used as the identifier in the declarator for a constructor declaration." Again. This is explicitly prohibited case. Sorry, but your comment about italics do not explain why typedef-name can't be used as a template-id. – Stas Dec 16 '10 at 21:09
  • Yeah I think according to the Standard his explicit specialization is indeed ill-formed. It's not using a template-id at all. What I'm wondering about is whether `template<> const char* const C::A::str = "B";` is valid. The Standard says that when the injected class name is used without arguments, it's equivalent to the injected class name followed by its template arguments. – Johannes Schaub - litb Dec 17 '10 at 07:06
  • This is how the injected class name gets dependent: " a template-id in which either the template name is a template parameter or any of the template arguments is a dependent type or an expression that is type-dependent or value-dependent.". Now syntactically `C::A` is not a template-id, but since it is said to be equivalent to it, the intent seems to be it is made dependent by making the corresponding template-id dependent :) Maybe we can apply the same logic to the above explicit-specialization constraint :) Although I can see the one about dependent types is more semantical than syntactical. – Johannes Schaub - litb Dec 17 '10 at 07:14
  • @Johannes: I think 2nd bullet under dependent type (in 14.6.2.1/1) covers the above case, and so the bullet re `template-id` is not needed: "a qualified-id with a nested-name-specifier which contains a class-name that names a dependent type or whose unqualified-id names a dependent type,". – Richard Corden Dec 17 '10 at 18:09
  • @Richard my above example was bad all the way. I mean, when you say `A` in the template definition's scope (which is what I had in mind, but not quite demonstrating above. Silly me): There, it will not be a qualified-id, still it needs to be dependent and it names the injected class name. – Johannes Schaub - litb Dec 17 '10 at 23:18
0

I guess explicit specialization is not the case there. You may write the similar code without templates:

struct A
{
    static const char* const str;
};

typedef A C;

const char* const C::str = "B";

This code is absolutely correct even if you use C instead of A. C is just an alias.

Just imagine how C++ code is processed. Typedefs are obviously expanded by compiler before template instantiation process is even started. The same as all comments are expanded before macros. As all macros are expanded by preprocessor before actual C++ parsing is even started. This simplifies everything.

Concerning C++03 standard, take a look at that

7.1.3/1 A name declared with the typedef specifier becomes a typedef-name. Within the scope of its declaration, a typedef-name is syntactically equivalent to a keyword and names the type associated with the identifier in the way described in clause 8. A typedef-name is thus a synonym for another type. A typedef-name does not introduce a new type the way a class declaration (9.1) or enum declaration does.

I guess this paragraph explains clearly that C in your code is actually a template-id.

Stas
  • 11,571
  • 9
  • 40
  • 58
  • 1
    This is not an equivalent example. "template<> int A::x;" is a special case as 'A' is the thing being explicitly specialized. In your example 'C' is just part of the nested-name-specifier, and there are no such restrictions on the names used in there. Additionally, there are words to allow for a typedef to be used where a 'class-name' is required, so in your quote from 7.1.3/1, "syntactically" doesn't imply that you can use a typedef everywhere you can use a class-name. – Richard Corden Dec 16 '10 at 19:54
  • @Richard Corden: I wouldn't agree with you. 7.1.3/4 is intended not to explicitly allow to use typedef-name as a class-name, but explicitly restrict typedef-name usage as a class-name in special cases. At the same time 7.1.3/1 explicitly specifies that typedef-name is a synonym and may be used everywhere if it is not restricted explicitly. – Stas Dec 16 '10 at 20:15
  • As per my comment above: Why doesn't 7.1.3/4 just start with: "If a typedef-name is used following..."? – Richard Corden Dec 16 '10 at 20:24