10

This question was inspired by this other question. While trying to answer that question, I understood that I have a lot of questions myself. So... Consider the following:

struct S1
{
    enum { value = 42 };
};

template <class T> struct S2
{
    typedef S1 Type;
};

template <class T> struct S3
{
    typedef S2<T> Type; 
};

template <class T> struct S4
{
    typedef typename T::Type::Type Type;  //(1)//legal?
    enum {value = T::Type::Type::value }; //(2)//legal?
};

int main()
{
    S4<S3<S2<S2<S1> > > >::value;
}

This compiles successfully with MSVC9.0 and Online Comeau. However, what's bothering me is that I don't understand what typename refers to in (1) and why wouldn't we need typename in (2).

I have tried these 2 syntaxes (syntaces?) of what I think it should be both of which fail on MSVC:

    typedef typename T::typename Type::Type Type;
    enum {value = typename T::typename Type::Type::value }; 

and

    typedef typename (typename T::Type)::Type Type;
    enum {value = (typename (typename T::Type)::Type)::value }; 

Of course, a workaround is to use consecutive typedefs like this:

   typedef typename T::Type T1;
   typedef typename T1::Type Type;
   enum { value = Type::value};  

Good style left aside, do we syntactically have to use the workaround I mentioned?

The rest is just an interesting example. No need to read. Not that relevant to the question.

Please note, that although MSVC accepts the original strange syntax without multiple typenames(I mean (1) and (2)), it leads to strange behavior as in the mentioned question. I think I'll present that example in concise form here as well:

struct Good
{
    enum {value = 1}; 
};
struct Bad
{
    enum {value = -1};  
};

template <class T1, class T2>
struct ArraySize
{
    typedef Bad Type;
};
template <class T>
struct ArraySize<T, T>
{
    typedef Good Type;
};

template <class T>
struct Boom
{
    char arr[ArraySize<T, Good>::Type::value]; //error, negative subscript, even without any instantiation
};

int main()
{
    Boom<Good> b; //with or without this line, compilation fails.
}

This doesn't compile. The workaround I mentioned solves the problem, but I am sure the problem here is my initial question - missing typename, but you don't really know where to stick one. Thanks very much in advance.

Community
  • 1
  • 1
Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434

5 Answers5

8

The name before the scope operator :: must always be a namespace or class (or enumeration) name, and namespace names can't be dependent. So you don't have to tell the compiler that this is a class name.


I'm not just making this up, the standard says (section [temp.res]):

A qualified name used as the name in a mem-initializer-id, a base-specifier, or an elaborated-type-specifier is implicitly assumed to name a type, without the use of the typename keyword. In a nested-name-specifier that immediately contains a nested-name-specifier that depends on a template parameter, the identifier or simple-template-id is implicitly assumed to name a type, without the use of the typename keyword. [ Note: The typename keyword is not permitted by the syntax of these constructs. — end note ]

T::, T::Type::, and T::Type::Type:: are nested-name-specifiers, they do not need to be marked with typename.

This section clearly could have, and arguably should have, included the type-specifier of a typedef declaration in the list of exemptions. But remember that type-specifiers can get really complicated, especially in typedef declarations. Right now it's quite possible to need the typename keyword multiple times in a typedef type-specifier, so a lot more analysis would be needed to convince me that typename is never necessary in a typedef.

In typedef typename T::Type::Type Type, T::Type::Type requires use of the typename keyword, because its nested-name-specifier (T::Type::) is a dependent name, and the standard says (same section):

When a qualified-id is intended to refer to a type that is not a member of the current instantiation (14.6.2.1) and its nested-name-specifier refers to a dependent type, it shall be prefixed by the keyword typename, forming a typename-specifier. If the qualified-id in a typename-specifier does not denote a type, the program is ill-formed.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • With the same logic I could say that after `typedef` only a type is expected so we wouldn't have to ever tell the compiler `typedef typename ...` So no, I'm not buying it :) – Armen Tsirunyan Jul 11 '11 at 07:29
  • I don't seem to find any confirmation of what you're saying. Could you please name chapter and verse? – Armen Tsirunyan Jul 11 '11 at 14:05
  • 3
    @Armen the quoted text was not part of C++03. But that was an accident. It was added as a result of a FCD comment about pointer to member declarators (`T Dependent::MyClass::*`) not allowing `typename`. The committee noticed the bigger picture problem and added the bugfix to all cases of nested name specifiers embedded into a qualified-id. (see http://www.justsoftwaresolutions.co.uk/cplusplus/c++0x-now-at-fcd.html at "19:00:31 on Saturday, 22 May 2010"). – Johannes Schaub - litb Jul 11 '11 at 21:17
  • Note that C++20 will allow `typename` to be omitted in many (but not all) places, including `typedef` (or any other declarator-based declaration) at namespace or class scope. – Davis Herring Aug 17 '19 at 05:12
3

The point of typename is to allow basic checking of a template definition before it is instantiated. Parsing C++ is impossible without knowing whether a name is a type or not (is a*b; an expression statement or a declaration of a pointee b).

In a template definition the category (type or non-type) of a simple identifier is always known. But a qualified (dependent) name cannot be - for arbitrary T, T::x could be either.

So the language allows you to tell the compiler that a qualified name represents a type by using the typename keyword. If you don't, the compiler is required to assume it to be a value type. In either case it is an error to mislead the compiler.

The rule applies even in some cases where it is obvious a type is required (eg in a typedef).

Only the full qualified name requires this disambiguation - typename A::B::C tells you C is a type; there's no need to know anything about A or B to parse the context in which the qualified name appears.

In your example (1) the typename is saying that T::Type::Type is a type. In (2) you must not use typename, because T::Type::value is not a type. Neither case says anything about T::Type because this is irrelevant. (Although one can deduce that it must be a type, because otherwise you couldn't apply :: to it.)

I suspect your problem with MSVC is simply a bug in that compiler (it's notorious that it doesn't handle two-phase lookup properly), although I have to admit I'm not 100% certain.

Alan Stokes
  • 18,815
  • 3
  • 45
  • 64
  • 1
    +1 for a thorough explanation. I still hope to see some Standard quote in one of the answer but it doesn't seem anybody had one handy. – Matthieu M. Jul 11 '11 at 06:16
  • 1
    So, what from 14.6 can you quote that back up your claim? – BЈовић Jul 11 '11 at 07:16
  • @VJo All of it! 14.6/3 and 14.6/5 make it clear that typename only affects a _qualified-id_. If you look at the grammar, `A::B::C` is a _qualified-id_ but the prefix `A::B` is not, so typename cannot affect it. (A _qualified-id_ is, loosely, a _nested-name-specifier_, a series of class/namespace names each followed by ::, followed by an _unqualified-id_.) – Alan Stokes Jul 11 '11 at 08:24
  • The prefix `A::B` is a qualified-id in the Standard too. The Standard calls any qualified name a *qualified-id* (which is a bit confusing, but is convenient). It does not depend on the parser tree of the nested-name-specifier (in which the non-terminal `qualified-id` does not appear). They are explicitly defined to be qualified-ids. For example "A nested-name-specifier that denotes a class, optionally followed by the keyword template (14.2), and then followed by the name of a member of either that class (9.2) or one of its base classes (Clause 10), is a qualified-id". – Johannes Schaub - litb Jul 11 '11 at 21:35
  • You can apply that quote to `A::B::`, if `A` is a class. Because `A::` is a nested-name-sepecifier followed by the name of a member of it. If you look around in the spec, it uses that row of definitions of "qualified-id" given by clause 5.1 everywhere. – Johannes Schaub - litb Jul 11 '11 at 21:39
  • @Johannes: I disagree. Eg 3.4.3.1/1 makes it clear that a qualified-id is one nested-name-specifier followed by a simple name. There's no way to parse A::B::C in a way which makes A::B a qualified-id, because a qualified-id cannot be followed by ::. Also, 9.2/1 makes clear that a nested class B is not a _member_ of surrounding class A, it is a nested type within it. So A::B is *not* a nested-name-specifier followed by the name of a member and 5.1/7 does not apply. (Nor should it, since it is talking about primary-expressions; A::B is not an expression if B is a class.) – Alan Stokes Jul 12 '11 at 10:51
  • @Alan you cannot make sense of the spec if you take that point of view (the rules are set such that you are forced to take my point of view in order to make sense of it, or I missed something very bad all the time, which I think is very unlikely). Also, you are wrong about the nested class B not being a member of the nesting class. It indeed is a member. – Johannes Schaub - litb Jul 12 '11 at 10:56
  • @Johannes: Hmm, 9.2/1 doesn't actually say that. However, I still don't think 5.1/7 is intended to override the grammar. It's only talking about expressions, and I don't believe "member" there is intended to cover nested types. – Alan Stokes Jul 12 '11 at 11:05
  • @Johannes: Can you show me somewhere where A::B in A::B::C is regarded as a qualified-id? – Alan Stokes Jul 12 '11 at 11:06
  • @JohannesSchaub-litb let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/1361/discussion-between-alan-stokes-and-johannes-schaub-litb) – Alan Stokes Jul 12 '11 at 11:06
  • @Alan I'm going to point you to a few places where the non-terminal "qualified-id" does not appear, but still is used to refer to the respective qualified name (it's easy to find more): 3.4.3.1p1, 3.4.3.2p1, 14.6p3 ("If the qualified-id in the typename-specifier ..."), 14.6.2.1p4bullet2, 14.6.2.1p6, 14.1p2 (typename A::B, where A::B will be a nested-name-specifier identifier grammatically. Grammatically, there is no qualified-id). – Johannes Schaub - litb Jul 12 '11 at 19:17
  • @Johannes I concede that the standard (ab)uses qualified-id in situations where the grammar contains "nested-name-specifier some-simple-id" rather than specifically the qualified-id non-terminal. But that doesn't mean that A::B in A::B::C is a qualified-id even in that extended sense. A::B::C is A::B:: followed by an identifier, which fits the pattern, but A:: followed by B::C does not. – Alan Stokes Jul 15 '11 at 16:24
2
typedef typename T::Type::Type Type;  //(1)//legal?
enum {value = T::Type::Type::value }; //(2)//legal?

in (1) you say that T::Type::Type is a type name

in (2) you say nothing about T::Type::Type::value and by default it will be parsed as non-type

knightmare
  • 103
  • 2
  • 6
-1

typedef typename T::Type::Type Type; //(1)//legal?

I myself don't understand the need of typename here. Becuase typedef can be applied only to a typename. Maybe C++ grammer is designed this way.

enum {value = T::Type::Type::value }; //(2)//legal?

You cannot use typename because, it's expected to be a value. It's implicitly logical, that when you write enum { value = ??? };, then ??? must always be a value only.

Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
iammilind
  • 68,093
  • 33
  • 169
  • 336
  • I think you have misunderstood my question. I ***do*** understand why we need typename in (1), I just don't understand why only 1. I think we need more. The same applies to (2). I know that value is a value, but `Type::Type` must be qualified with `typename` before we can extract its `value` – Armen Tsirunyan Jul 10 '11 at 17:56
  • @Armen, When we are scoping towards `value` using `::` (i.e. `T::Type::Type::value`), how can one use `typename`. It would have been a very unusal syntax, if it was there. – iammilind Jul 10 '11 at 18:03
  • I need typename because both `T::Type` and `T::Type::Type` are dependent names – Armen Tsirunyan Jul 10 '11 at 18:06
-4

The typename refers to the first dependent type. In your particular case :

typedef typename T::type1::type2 Type;

it refers to T::type1, telling it is a dependent name (depending on the template parameter T).

For a constant value, you do not need a typename, because it is a value - not a type. If the value is not defined, you'll get a compilation error.

EDIT

struct S1
{
    enum { value = 42 };
};
template <class T> struct S2
{
    typedef S1 Type;
};
template <class T> struct S3
{
    typedef S2<T> Type; 
};
template <class T> struct S4
{
    typedef typename T::Type::Type Type;  //(1)//legal?
    enum {value = T::Type::Type::value }; //(2)//legal?
};

Lets go slowly through the example. What happens in this S4<S3<S2<S2<S1> > > > type is this : since T is S3<S2<S2<S1> > >, then typename T::Type expands to S2<S2<S1> >::Type, which is a full type (not depending in any way on the template parameter any further). For that reason you do not need to use typename after the first dependent typename.

BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • In this case, I definitely need a typename in context like this: `typename T::Type1::value1` if typename refers to Type1, right? And second, How do I tell the compiler that `Type2` is also a typename in `T::Type1::Type2` – Armen Tsirunyan Jul 10 '11 at 18:05
  • @Armen Well, you tell that `Type2` is a type, exactly like you did. It doesn't depend on the template parameter, but it should be defined within the `Type1` somehow. – BЈовић Jul 10 '11 at 18:14
  • I don't think you're right. If the compiler needs to be told that `T::Type1` is a type, then it also should be told that `T::Type1::Type2` is a type (not an enumerator, for example) – Armen Tsirunyan Jul 10 '11 at 18:16
  • @Armen Regarding the constant, if you use `typename` it should just be ignored by the compiler. – BЈовић Jul 10 '11 at 18:18
  • @Armen But you are telling that `Type2` should be a type defined within `Type1`, which is declared within the template argument T. If you pass an enum, the compilation will fail at the moment of the template instantiation. – BЈовић Jul 10 '11 at 18:21
  • It's not that simple. If what you describe were the case we wouldn't need the typename keyword at all. Ever heard of two-phase name lookup? – Armen Tsirunyan Jul 10 '11 at 18:23
  • 1
    @VJo @Armen I'm convinced `typename T::Type1::Type2` is declaring that Type2 is a type, not Type1. See 14.6 in the standard. – Alan Stokes Jul 10 '11 at 21:56