mgio

The following code won't compile and it isn't clear to me why:

/******************************************************************************/

// NullType: Used to signify the end of a list

/******************************************************************************/

// This is from Modern C++ Design

class NullType {};

/******************************************************************************/

// EmptyType: Used to pass an empty (null) value

/******************************************************************************/

// This is from Modern C++ Design

struct EmptyType {};

/******************************************************************************/

// Typelist: Foundation for building lists of types

/******************************************************************************/

// This is from Modern C++ Design

template <class T, class U>

struct Typelist

{

typedef T Head;

typedef U Tail;

};

/***********************************************************************************************/

// GenerateHierarchy: Generates a class hierarchy with the specified class types

/***********************************************************************************************/

// just the forward declaration here

template <class TList,

template <class AtomicType, class BaseType> class Unit,

class Root = EmptyType>

class GenerateHierarchy;

/***********************************************************************************************/

// GenerateHierarchy: Generates a class hierarchy with the specified class types

/***********************************************************************************************/

// This is from Modern C++ Design where it is call GenLinearHierarchy

// It's prefered over GenScatterHierarchy because it doesn't use multiple inheritance.

template <class T1, class T2,

template <class, class> class Unit,

class Root>

class GenerateHierarchy<Typelist<T1, T2>, Unit, Root>

: public Unit<T1, GenerateHierarchy<T2, Unit, Root> >

{

public:

GenerateHierarchy<Typelist<T1, T2>, Unit, Root>(void) : Unit<T1, GenerateHierarchy<T2, Unit, Root> >() {}

};

template <template <class, class> class Unit,

class Root>

class GenerateHierarchy<NullType, Unit, Root>

: public Root

{

public:

GenerateHierarchy<NullType, Unit, Root>(void) : Root() {}

};

class A

{ };

// List of classes to be included in the hierarchy

typedef Typelist< A, NullType > ClassList;

/***********************************************************************************************/

// Base: Class for the base of a hierarchy

/***********************************************************************************************/

class Base

{

};

/***********************************************************************************************/

// Holder: A container to hold each class in the hierarchy

/***********************************************************************************************/

template <class T, class BaseType>

class Holder : public BaseType

{

public:

Holder(void) : BaseType() {}

T obj;

};

/***********************************************************************************************/

// Container: The master container that will contain all the classes by inheritance

/***********************************************************************************************/

class Container : public GenerateHierarchy<ClassList, Holder, Base>

{

public:

Container(void) : GenerateHierarchy<ClassList, Holder, Base>() {}

};

Trying to compile this gives the errors:

------ Build started: Project: MS_Bug, Configuration: Debug Win32 ------

Compiling...

main.cpp

c:\documents and settings\gio\my documents\visual studio 2005\projects\ms_bug\ms_bug\typelists.h(88) : error C3200: 'Holder<T,BaseType>' : invalid template argument for template parameter 'Unit', expected a class template

with

[

T=A,

BaseType=GenerateHierarchy<NullType,Holder,Base>

]

c:\documents and settings\gio\my documents\visual studio 2005\projects\ms_bug\ms_bug\typelists.h(88) : error C2614: 'Container' : illegal member initialization: 'GenerateHierarchy<Typelist<A,NullType>,Holder<A,GenerateHierarchy<NullType,Holder,Base> >,Base>' is not a base or member

MS_Bug - 2 error(s), 0 warning(s)

========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

The line that is causing the error is:

Container(void) : GenerateHierarchy<ClassList, Holder, Base>() {}

Commenting out this line and thus removing the explicit constructor definition gets rid of these errors and the code will compile and run without issue.

Apparently, the compiler does not think that the base constructor call of GenerateHierarchy<ClassList, Holder, Base>()

is valid. From looking at the definition of the class "Container" you can obviously see that this is correct since it clearly inherits from GenerateHierarchy<ClassList, Holder, Base>. For some reason, the compiler appears to expand the "Holder" parameter to GenerateHierarchy, expanding it to Holder<A,GenerateHierarchy<NullType,Holder,Base> > instead of treating as a template class itself and leaving it as Holder. In other words, the base type is: GenerateHierarchy<ClassList, Holder, Base>, but the compiler interprets the base constructor call as: GenerateHierarchy<Typelist<A,NullType>,Holder<A,GenerateHierarchy<NullType,Holder,Base> >,Base>, when I believe it should be: 'GenerateHierarchy<Typelist<A,NullType>, Holder, Base>.

This code will compile fine if the explicit constructor definition is removed from Container class, and that is a reasonable solution in this case since the constructor is empty anyways, but that is not a valid solution in practice.

Note that this code compiles without any issues under GCC.

Does anyone know why this is happening Is it a bug in Microsoft VC++



Re: Visual C++ Language Issues with template classes and constructors in VC++

Brian Kramer

I think its a bug, a VC holdover from the days where VC took more liberties in inferring template parameters. It sounds like the only reason you need to be explicit on the parameters (rather than letting VC++ know what you mean by the naked GenerateHeirarchy) is because GCC (and probably C++ in general) requires it.

In my experiences in cross-compiling code in VC, I initially use the naked template name, and later add the extra parameters after discovering that GCC expects it. In this particular case, it seems like you'll have to #ifdef two cases, one to workaround the VC bug (if there truly is one) by using its leniency, and one for GCC where you are explicit on the parameters.





Re: Visual C++ Language Issues with template classes and constructors in VC++

mgio

Ah, you are correct. If I change the constructor to be:

Container(void) : GenerateHierarchy() {}

then it compiles. I hadn't thought of trying that. Of course, this won't compile under GCC, so I will have to do what you say and #ifdef it for the two different compilers. It seems kind of scary that VC accepts the call without the template parameters though I guess it gets that info from the base type declaration. I suppose I learned it backwards from the way you did; I know that GCC requires the parameters and now I'm discovering that VC prohibits them.

Thanks so much for your help.





Re: Visual C++ Language Issues with template classes and constructors in VC++

Brian Kramer

" I know that GCC requires the parameters and now I'm discovering that VC prohibits them."

That's not really true. In general, VC should accept the explicit naming of template parameters for base class (whether it should *require* it like GCC does, I don't know). I think in your particular case, there's a VC bug where it's not letting you.





Re: Visual C++ Language Issues with template classes and constructors in VC++

mgio

ok, that makes sense since in all other instances I've been able to pass the template parameters to a base constructor without issue in VC++.




Re: Visual C++ Language Issues with template classes and constructors in VC++

mgio

I've tried the Comeau online compiler (at http://www.comeaucomputing.com/tryitout/ ) with the original code I posted here and it gives a very similar error to the VC++ one:

Comeau C/C++ 4.3.9 (Mar 27 2007 17:24:47) for ONLINE_EVALUATION_BETA1
Copyright 1988-2007 Comeau Computing. All rights reserved.
MODETongue Tiedtrict errors C++ C++0x_extensions

"ComeauTest.c", line 74: error: type "Holder<T, BaseType>::Holder [with T=A,
BaseType=GenerateHierarchy<NullType, Holder, Base>]" is not a class
template
Container() : GenerateHierarchy<ClassList, Holder, Base>() {}
^

"ComeauTest.c", line 74: error: "GenerateHierarchy" is not a nonstatic data member
or base class of class "Container"
Container() : GenerateHierarchy<ClassList, Holder, Base>() {}
^

2 errors detected in the compilation of "ComeauTest.c".

People are telling me that there is no way that the two compilers could have the same bug, but I still think it's a compiler bug.




Re: Visual C++ Language Issues with template classes and constructors in VC++

Jonathan Caves - MSFT

This is not a bug - it is a valid error. It all comes down to name-lookup (as most thinks do in C++). This is the problem code:

Code Snippet

class Container : public GenerateHierarchy<ClassList, Holder, Base>

{

public:

Container()

: GenerateHierarchy<ClassList, Holder, Base>()

{

}

};

The issue is that this code causes a specialization of Holder to be become an indirect base-class of Container. Now as the direct base class (and all the indirect base-classes) of Container are not dependent base-classes (all there template-arguments are real types) name lookup for unqualified names with Container is allowed to look inside the base-classes. Also C++ has the rule about the injected class name - each class has a pseudo-member which has the name of the enclosing class - furthermore for a specialization of a class template the injected name is both the raw class name (say Holder) and the specialized form (Holder<...>). So adding all this together the name lookup for identifier Holder within the body of Container is finding the injected class name for the base class Holder - not the global class template Holder. The easiest fix for this issue is to change the code to:

Code Snippet

class Container : public GenerateHierarchy<ClassList, Holder, Base>

{

public:

Container()

: GenerateHierarchy<ClassList, ::Holder, Base>()

{

}

};

This will force name-lookup to find the class-template. This is not a problem in the base-class list as the name lookup for Holder happens before the base-class is added to the class.






Re: Visual C++ Language Issues with template classes and constructors in VC++

Brian Kramer

Thanks for the clarification, Jonathan.

Is there anything special in VC++ (special as in beyond what the the C++ standard says) that allows one to omit the explicit naming of template parameters when referring to a base class this way

i.e. why does the following also work

class Container : public GenerateHierarchy<ClassList, Holder, Base>

{

public:

Container()

: GenerateHierarchy()

{

}

};





Re: Visual C++ Language Issues with template classes and constructors in VC++

Jonathan Caves - MSFT

This should work as the name-lookup for GenerateHierarchy will find the injected class name for the base-class. Unfortunately Visual C++ gives an error on this - as does EDG in "non strict" mode.






Re: Visual C++ Language Issues with template classes and constructors in VC++

mgio

Jonathan Caves - MSFT wrote:

This is not a bug - it is a valid error. It all comes down to name-lookup (as most thinks do in C++). This is the problem code:

Code Snippet

class Container : public GenerateHierarchy<ClassList, Holder, Base>

{

public:

Container()

: GenerateHierarchy<ClassList, Holder, Base>()

{

}

};

The issue is that this code causes a specialization of Holder to be become an indirect base-class of Container. Now as the direct base class (and all the indirect base-classes) of Container are not dependent base-classes (all there template-arguments are real types) name lookup for unqualified names with Container is allowed to look inside the base-classes. Also C++ has the rule about the injected class name - each class has a pseudo-member which has the name of the enclosing class - furthermore for a specialization of a class template the injected name is both the raw class name (say Holder) and the specialized form (Holder<...>). So adding all this together the name lookup for identifier Holder within the body of Container is finding the injected class name for the base class Holder - not the global class template Holder. The easiest fix for this issue is to change the code to:

Code Snippet

class Container : public GenerateHierarchy<ClassList, Holder, Base>

{

public:

Container()

: GenerateHierarchy<ClassList, ::Holder, Base>()

{

}

};

This will force name-lookup to find the class-template. This is not a problem in the base-class list as the name lookup for Holder happens before the base-class is added to the class.

Wow, thanks for the answer. I'm struggling to understand what you are saying, but it makes sense so far. First, you are saying that since the base classes of Container are not dependent, meaning their template parameter calues are fully known when Container is compiled, unqualified name lookups are allowed to look inside the base classes of Container. So when it goes to look up "Holder" it finds the pseudo-members named "Holder" in the base classes which are the specilizations of Holder. It chooses the specialized "Holder" instead of the global "Holder" (is this the expected behavior always or is it just ambiguous which it chooses) and that "Holder" isn't the Holder I want. The additional of the "::" makes it refer to the global "Holder" and removes the ambiguity. This problem doesn't exist when Holder is referenced in the base-class list becase at that time the Container class does not exist yet and so there is no "Holder" pseudo-member to be found in the class hierarchy. Did I understand that correctly

I guess the key idea here is that derived class inherit all the types from their base classes if the base classes are not dependent. I've certainly run into the opposite issue before when I've tried to reference members of a dependent base class only to learn the hard way that I need to fully qualify them in order for the compiler to find them.

What I suppose is surprising me the most here is that the compiler will find the specialization of Holder in the base class and try to match it to the "Holder" I am referencing in the Container class constructor call. I suppose the specialization implicitly declared in the class hierarchy takes precedence over the global "Holder" but I'm confused as to why it matches at all. If I wanted the specialized "Holder", I would explicitly ask for it by supplying the full set of template parameters for it.

I wonder if this is really highlighting the difference between GCC and VC++ that was pointed out earlier in the thread. In VC++ I can use call "GenerateHierarchy()" in the constructor definition as was pointer out by Brian instead of explicitly specificying the template parameters and VC++ figures out what I mean, I suppose by looking at the base-class list. Likewise, I'm assuming for the same reason VC++ allows the "Holder" reference in the constructor and assumes it to mean Holder<B,GenerateHierarchy<Typelist<C,Typelist<I,Typelist<J,NullType> > >,Holder,Base> > instead of the global "Holder". But why is it making that assumption GCC is stricter and requires me to explicitly list the template parameters used for the constructor call to GenerateHierarchy. Is it this same behavior that prevents GCC from assuming "Holder" refers to the specialization of Holder which appears in the base classes and thus must refer to the global Holder If so, it seems like it is VC++ leniency that is causing the problem. Of course, I'm not sure which one is actually correct; is this leniency the correct behavior or not I guess I'll have to look at the spec.

Finally, this explains why the following code compiles fine:

typedef GenerateHierarchy<ClassList, Holder, Base> ContainerBaseType;

/***********************************************************************************************/

// Container: The master container that will contain all the classes by inheritance

/***********************************************************************************************/

class Container : public ContainerBaseType

{

public:

Container() : ContainerBaseType() {}

};

Since the typedef is outside the Container class, the Holder refered to is the global Holder and not the Holder in the class hierarchy. This confused me greatly as I believed the typedef version should be identical to broken version.

So I guess it all makes sense now, but how would I ever have figured this out. Can this all be deduced from the spec or are there any good books out there that might shed some light on these issues Or do I basically have to be a compiler writer to know this Smile Thanks again.





Re: Visual C++ Language Issues with template classes and constructors in VC++

Jonathan Caves - MSFT

You almost have it: the key concept you appear to be missing is 'class name injection' this link may help. Also name lookup in C++ is based on the rule that the first element found wins (ignoring multiple inheritance for the moment) - so once the compiler finds the name in the base class it doesn't look any further - specifically it doesn't look in namespace scope.

The Visual C++ compiler seems to get this right in some cases as does GCC - the problem is that neither compiler gets the same subset correct and both compilers have bugs.

The can all be figured out from a "close and careful" reading of the C++ Standard .






Re: Visual C++ Language Issues with template classes and constructors in VC++

mgio

Jonathan Caves - MSFT wrote:

You almost have it: the key concept you appear to be missing is 'class name injection' this link may help. Also name lookup in C++ is based on the rule that the first element found wins (ignoring multiple inheritance for the moment) - so once the compiler finds the name in the base class it doesn't look any further - specifically it doesn't look in namespace scope.

The Visual C++ compiler seems to get this right in some cases as does GCC - the problem is that neither compiler gets the same subset correct and both compilers have bugs.

The can all be figured out from a "close and careful" reading of the C++ Standard .

Thanks for the link. I actually have that book sitting on my desk next to me. I guess I should go and read it more carefully.