Template Fun

20 replies to this topic

#1eddie

Senior Member

• Members
• 751 posts

Posted 04 May 2006 - 02:33 PM

Anyone tell me why this is borking?


#include <map>

template<typename MyClass>

class TemplateClass

{

public:

typedef void (MyClass::*FunctionType)();

typedef std::map<int, FunctionType> MyTypedef;

void MyFunction()

{

//MyTypedef::iterator iter; // Dies: "main.cpp|13| error: expected ';' before "iter"

}

};

class MySpecializedClass : public TemplateClass<MySpecializedClass>

{

public:

void MyFunction()

{

MyTypedef::iterator iter; // Works fine.

}

};

int main(void)

{

return 0;

}



I'm a little confused as to why the specialized class can make an iterator, but the superclass can't.

Weird.

#2SmokingRope

Valued Member

• Members
• 210 posts

Posted 04 May 2006 - 03:08 PM

It looks good to me. Maybe try it out with a different type of container? I'm not sure about the map but it's definitely possible to do the same thing with vector and list????

#3monjardin

Senior Member

• Members
• 1033 posts

Posted 04 May 2006 - 03:45 PM

It works fine with VC7.1 and VC8.
monjardin's JwN Meter (1,2,3,4,5,6):
|----|----|----|----|----|----|----|----|----|----|
*

#4eddie

Senior Member

• Members
• 751 posts

Posted 04 May 2006 - 03:46 PM

Doesn't work with any of the STL things.

I may have to go low-tech and just build this using arrays of pointers.

#5monjardin

Senior Member

• Members
• 1033 posts

Posted 04 May 2006 - 04:03 PM

What compiler are you using? GCC?
I can even get this to work:


#include <map>

#include <iostream>

template<typename MyClass>

class TemplateClass

{

public:

typedef void (MyClass::*FunctionType)();

typedef std::map<int, FunctionType> MyTypedef;

void MyFunction()

{

MyTypedef m;

m.insert(std::make_pair(0, &MyClass::foo));

MyTypedef::iterator it = m.begin();

(reinterpret_cast<typename MyClass*>(this)->*it->second)();

}

};

class MySpecializedClass : public TemplateClass<MySpecializedClass>

{

public:

void foo()

{

std::cout << "foo" << std::endl;

}

};

int main(void)

{

MySpecializedClass c;

c.MyFunction();

return 0;

}



Here is a picture of eddie working on his lua binding code: :wallbash:
monjardin's JwN Meter (1,2,3,4,5,6):
|----|----|----|----|----|----|----|----|----|----|
*

#6.oisyn

DevMaster Staff

• Moderators
• 1842 posts

Posted 04 May 2006 - 04:05 PM

MyTypedef::iterator is a dependent name, the compiler doesn't know at point of definition whether that's a type or a function/variable. This is because you are able to define a specialization of std::map with a different meaning of the 'iterator' identifier, so the meaning of MyTypedef::iterator totally depends on the template parameter of MyClass, which isn't known at time of definition. But the type is known when instantiating the template, which is why it works in the subclass.

So, you need to give the compiler a hint whether you mean a type or a function/variable: if you mean a type, use the typename keyword:

    void MyFunction()
{
typename MyTypedef::iterator iter;
}
Otherwise, the compiler assumes MyTypedef::iterator is a function or variable (which explains your compile error)
-
Currently working on: the 3D engine for Tomb Raider.

#7AndyP

New Member

• Members
• 15 posts

Posted 04 May 2006 - 04:05 PM

Wierd - it looks fine. After all, you don't appear to be calling the template base class MyFunction() method from anywhere, meaning it shouldn't ever be instanced, and therefore should not generate any compile errors, even if it contains utter rubbish.

I shall assume that you are in fact calling it from somewhere else, and your compiler is doing something odd and non-standard.

Two suggestions to fix:

1) Prefix MyTypedef::iterator with the 'typename' keyword:

// in TemplateClass

void MyFunction()

{

typename MyTypedef::iterator iter;

}


'typename' is a hint to the compiler that the following identifier is a type.

2) If that fails, try typedef-ing the map iterator itself:

template <typename MyClass>

class TemplateClass

{

public :

typedef void (MyClass::*FunctionType)();

typedef std::map<int,FunctionType> MyTypedef;

typedef typename MyTypedef::iterator MyIteratorTypedef;

void MyFunction()

{

MyIteratorTypedef iter;

}

};
(don't forget the typename before the MyTypedef::iterator typedef - some compilers let you get away without, but not all)

#8AndyP

New Member

• Members
• 15 posts

Posted 04 May 2006 - 04:07 PM

sorry .oisyn, you were a couple of seconds quicker to the post :blush:

#9.oisyn

DevMaster Staff

• Moderators
• 1842 posts

Posted 04 May 2006 - 04:09 PM

AndyP said:

After all, you don't appear to be calling the template base class MyFunction() method from anywhere, meaning it shouldn't ever be instanced, and therefore should not generate any compile errors, even if it contains utter rubbish.
Not entirely correct. Semantics shouldn't need to be correct when you don't call the function, but it should definitely parse correctly. And because the compiler assumes a function or variable instead of a type, you'll get parse errors. Therefore, correct use of the typename keyword is mandatory.
-
Currently working on: the 3D engine for Tomb Raider.

#10AndyP

New Member

• Members
• 15 posts

Posted 04 May 2006 - 05:41 PM

Hi .oisyn
I was about to post with a thank you for a point well made, but I've just done a quick test in MSVC7.0, and it appears that a template function body can include anything , so long as it's just lexically correct! Of course, when you instance the template it complains like crazy...
I think that the important difference is between type references in a template class body, where a type reference can be syntactically ambiguous and requires semantic information to resolve (using 'typename', or by being a known non-template-typedef), and a template method body, where it need not be. MSVC says - we'll parse template methods properly when they are instanced, but template classes must be up-front syntactically correct at their point of definition.

template <typename T>

void ParsesJustFine()

{

typedef std::vector<T> TVec;

typedef TVec::iterator TIter; // No typename required

}

template <typename T>

void ParsesOKToo()

{

1 2 3 ? ? ? // crazy

}

template < typename T >

class RequiresTypename

{

typedef std::vector< T > TVec;

typedef TVec::iterator TIter;   // Whoops - needs a typename

};



#11eddie

Senior Member

• Members
• 751 posts

Posted 05 May 2006 - 06:42 AM

Sorry, just tried this out now.

Yes, the typedef fix that .oisyn mentioned made it work. Kinda cryptic error tho. Oh well - at least I know how to deal with it from now on in. ;)

Cheers!

#12SmokingRope

Valued Member

• Members
• 210 posts

Posted 05 May 2006 - 09:46 AM

I agree, highly interesting tidbit!

#13.oisyn

DevMaster Staff

• Moderators
• 1842 posts

Posted 05 May 2006 - 01:46 PM

AndyP: MSVC++ is hardly any proof of how things should be handled . Although stricter typename rules were introduced in 7.1. But eddie's code in the topicstart (with the erroneous line not being commented out of course) is not correct ISO C++.

MSVC++ fails on this related point as well btw:
void foo(double);

template<class T> void bar()
{
foo(34);
}

void foo(int);

The above code should always call foo(double) (ISO C++ paragraph 14.6.3 - Non-dependent names). MSVC++, however, takes foo(int) into account when instantiating bar after the declaration of foo(int). This is because MSVC++ simply doesn't 'compile' the template only until it's instantiated, which may be an easier approach implementation-wise, but surely isn't correct
-
Currently working on: the 3D engine for Tomb Raider.

#14bramz

Valued Member

• Members
• 189 posts

Posted 05 May 2006 - 03:42 PM

Andy,

It has nothing to do with methods vs classes. The moment you want to use a type name that's _dependent_on_a_template_parameter, and that _could_be_interpreted_as_a_static_member_, you have to use typename.

in your ParsesJustFine, TVec depends on T, which is a template parameter, so you have to use typename.

The reason why you have to use it, is because you can have things like the following:


template <typename T>

struct A

{

typedef int foo;

};

template <>

struct A<void>

{

static int foo;

};

template <typename T>

void fun(const A<T>& a)

{

int bar;

// while compiler parses the following line,

// it must know if it has to parse it

// as a declaration (bar is a pointer to foo),

// or as a multiplication (foo * bar)

// if you say nothing, it assumes the latter.

A<T>::foo * bar; // multiplication

typename A<T>::foo * bar; // pointer to foo

}



Of course, this code will never compile, since it wants foo to behave as both a static member and a type name.
hi, i'm a signature viruz, plz set me as your signature and help me spread :)
Bramz' warehouse | LiAR isn't a raytracer

#15eddie

Senior Member

• Members
• 751 posts

Posted 05 May 2006 - 04:57 PM

.oisyn said:

But eddie's code in the topicstart (with the erroneous line not being commented out of course) is not correct ISO C++.

But it *will* be, when it grows up. ;)

That's actually why I wanted to figure out what was going wrong. General compiler behaviours I understand, but template craziness always leaves me straining. I figured that I could get it working under VC, but that's almost always not a sign of adherance to The Standard.

One day I'll just intuitively know, instead of simply using my memory to catalogue all the corner-cases... I hope. ;)

#16AndyP

New Member

• Members
• 15 posts

Posted 05 May 2006 - 07:23 PM

bramz: Good example! I don't dispute that there are cases when 'typename' must be used to disambiguate syntax for dependent names; I was suggesting that MSVC is a lot more forgiving when it comes to parsing template method bodies than it is with class bodies (incidentally, this behaviour appears to cross over to other compilers, like the gnu and sn ones we use at work). For example, your example compiles fine for MSVC7, for both A<int> and A<void>, if the "//pointer to foo" line is removed, and "int bar;" is made a global variable instead - i.e. even in this case, 'typename' is unnecessary, because as .oisyn says, it's parsed at the point of instance, not at the point of definition.

.oisyn: That's a very interesting consequence of the apparent MSVC template method parsing rules that I hadn't considered. For some reason, I was under the illusion that 7.0+ was far more standards compliant than previous versions (actually, come to think of it, that's probably true - 6.0 was awful :happy: )

#17bramz

Valued Member

• Members
• 189 posts

Posted 05 May 2006 - 10:27 PM

AndyP, maybe you missed the point: MSVC doesn't obey the standard on that matter. It is _not_allowed_ to consider A<T>::foo as a type if it isn't preceded by the keyword typename! It _has_ to treat it as a static member, and thus fail to compile if (at instantiation) it turns out to be otherwise.

Greetz,
Bramz
hi, i'm a signature viruz, plz set me as your signature and help me spread :)
Bramz' warehouse | LiAR isn't a raytracer

#18.oisyn

DevMaster Staff

• Moderators
• 1842 posts

Posted 07 May 2006 - 08:48 PM

Andy: correct, MSVC++ 7.0 is more compliant than 6.0, but 7.1 in turn is more compliant than 7.0, especially regarding templates. Partial specializations were only introduced until 7.1, and that in my opinion was a major breakthrough in the msvc++ series.

bramz: about the 'at instantiation' part, were you talking in the mere context of your example or in general? Because in general, of course, some errors can be detected at the definition, like eddie's line of code (if you assume it is a non-type member, which you should because of the missing 'typename', it yields a parse error).

BTW, for the very same reason you have to include the 'template' keyword when a dependent name is a template:
template<class T> struct S
{
typedef typename T::template foo<int> foo_int;
// as a type, no ambiguity here, but without 'template' this is an error

void f()
{
int i = T::bar<3>(5);
// do you mean T::bar as a template function,
// or (T::bar smaller-than 3) larger-than 5?
// the latter is assumed, if you mean the former, use:
int i = T::template bar<3>(5);
}
};

-
Currently working on: the 3D engine for Tomb Raider.

#19AndyP

New Member

• Members
• 15 posts

Posted 08 May 2006 - 07:56 AM

bramz: Don't worry I got your meaning first time :happy: I understand that MSVC is a special case and is not standards compliant - I was just trying to determine exactly what it was doing. It's way of parsing at instanciation, 'typename' not required, has some interesting consequences, as .oisyn pointed out.

#20bramz

Valued Member

• Members
• 189 posts

Posted 08 May 2006 - 10:52 PM

.oisyn said:

bramz: about the 'at instantiation' part, were you talking in the mere context of your example or in general? Because in general, of course, some errors can be detected at the definition, like eddie's line of code (if you assume it is a non-type member, which you should because of the missing 'typename', it yields a parse error).

I was talking in general. So, the code has been parsed successfully because the programmer has correctly used the keyword typename (or didn't use for that matter), but the actual template parameter fails to live up to this expectations. For example bar<void> would define foo as a static member while typename A<T>::foo * bar wants it as a type name. This, of course, you already know. It's just to explain what I was saying =)

Nice example for the template keyword thing. Although it occurs less frequently, it is indeed very similar.
hi, i'm a signature viruz, plz set me as your signature and help me spread :)
Bramz' warehouse | LiAR isn't a raytracer

1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users