Skip to main content
gamedev.net
🔒 Locked

Quick templates question

Started by spraff Mar 12, 2009 at 10:05 AM 13 replies 1.7k views
Original Post
spraff
spraff
Hello. Here's my problem distilled:

template <int i, typename T>
class Foo {
public:
	enum Enum {A,B,C};
	void bar (Enum);
};

template <typename T>
void Foo<0,T>::bar (typename Foo<0,T>::Enum) { // Error here
}

This doesn't compile -- "invalid use of incomplete type class Foo<0, T>" -- but I don't understand why. Thanks if you can explain this...
gfxnomad
gfxnomad
spraff,

You can't partially specialize a member function of a templatized class. You must partially specialize the class template instead. Check the code below, it should compile:

template <int i, typename T>class Foo {public:	enum Enum {A,B,C};	void bar (Enum);};template <typename T>class Foo<0,T> {public:	enum Enum {A,B,C};	void bar (Enum);};template <typename T>void Foo<0,T>::bar ( Foo<0,T>::Enum) { // Error here}



It sort of sucks, but that's the way things work (for now).

Cheers,
Rob
NotAYakk
NotAYakk
You can get around some of this as follows:
class FooBase {public:  enum Enum {A,B,C};};template <int i, typename T>class Foo: public FooBase {  void bar (Enum);};template <typename T>class Foo<0,T>: public FooBase {public:  void bar (Enum);};template <typename T>void Foo<0,T>::bar ( Foo<0,T>::Enum) {}

Ie, factor out the common parts of Foo and move it to FooBase.

You can also do a jump-through-a-hoop trick by having bar redirect to a (possibly private) template functor being passed the template parameters of Foo, and do partial template specialisation on the bar functor class to catch the zero case and deal with it differently.

But really, do you want to?
spraff
spraff
Thanks for clearing that up.

I want to avoid following your solution exactly since my actual code is considerably more complex, so I want to exploit inheritence to tidy things up. Here's what I'm trying to work with now:
// X and Y show expected inheritence of Enum.class X {public:	enum Enum {a,b};};class Y : public X {public:	void foo (Enum); // #1};// "Base" class is very large, don't want to// duplicate it, "Enum" represents all that.template <int i, typename T>class Base {public:	enum Enum {A,B,C};};// Except for special values of I, Foo is// equivalent to Base.template <int I, typename T>class Foo : public Base<I,T> {public:};// Special behaviour for specific I.// Note that line #2 should behave as #1// but instead it generates an error:// "Enum has not been declared"template <typename T>class Foo<0,T> : public Base<0,T> {public:	void bar (Enum); // #2};template <typename T>class Foo<1,T> : public Base<0,T> {public:	void bar (Enum, int);};template <typename T>void Foo<0,T> :: bar (typename Foo<0,T>::Enum) {	std::cout << "Foo<0,>\n";}template <typename T>void Foo<1,T> :: bar (typename Foo<1,T>::Enum,int) {	std::cout << "Foo<1,>\n";}typedef Foo<0,float> A;typedef Foo<1,float> B;int main () {	A a;	B b;	a.bar(A::C);	b.bar(B::C,0);	return 0;}

Can you explain why Base::Enum isn't visible to Foo?
NotAYakk
NotAYakk
As a template-argument dependant type, prior to knowing the template arguments your code cannot determine if it is a type or not. Nor can it determine if you are referring to a symbol defined in your parent, or a global symbol, at the time that the template is parsed.

You tell the compiler that something is actually a type using the typename keyword.

You tell the compiler that your symbol refers to something in your parent by naming your parent explicitly.

template<typename T>struct Base {  struct foo {};};typedef enum { one = 1 } foo;template<typename T>struct Child: Base<T> {  typedef Base<T> parent;  void bar( typename parent::foo );  typedef typename parent::foo foo;  void baz( foo );};

Base is a template class. It defines a type Base::foo.

Child inherits from Base. I do a typedef of Base to parent to get a shorter, less cumbersome name for the class I inherited from (in your case, this is more important, as you have multiple parameters).

When I wrote the bar function, I fully qualified the foo type so that the parser could determine that I am referring to something from my parent, and not the foo that is visible at global scope.

I had to use typename, because as far as the compiler knows, the symbol parent::foo could be an enum. Ie, some ass later could write:
template<>struct Base<int> {  enum { foo = 7 };};

at the time that I actually instantiate the Child template. In general, parent::foo being an enum or a static or a method or an enum value is ambiguous from context, and can change what the parse tree looks like for the template. So I have to provide the compiler with the hint that it is a typename.

Tired of typing out typename all of the time, I simply use a typedef to bring the foo name into my own scope, and can now use foo without problem.
spraff
spraff
Ah, thanks a lot. This is making more sense now. There's one more detail that needs to be ironed out: default function arguments. The "Enum" enumeration is defined in the template base class, and referring to Enum requires "typename Base::Enum".

That's for when you need to tell the compiler that a symbol is a type. What about when you need to tell the compiler that a symbol is NOT a type?
template <typename T>class Foo<0,T> : public Base<0,T> {public:	typedef typename Base<0,T>::Enum Enum;	void bar (Enum=B); // B is not defined.};

A similar error, but the opposite problem? What's the solution this time, O wise ones? :-)
magic_man
magic_man
void bar (Enum=B);

Are you trying to give it a default value here as that line does not make any sense.
"You insulted me!" I did not say that in the private message Tom Sloper!
spraff
spraff
Quote:
Original post by magic_man
void bar (Enum=B);

Are you trying to give it a default value here as that line does not make any sense.

Yes. Perhaps I should have re-posted an earlier snippet:
template <int i, typename T>class Base {public:	enum Enum {A,B,C};};


[Edited by - spraff on March 13, 2009 8:12:07 AM]
NotAYakk
NotAYakk
I'm going to use the fish trick, rather than answering the last question.
struct foo {  enum stuff {A, B, C};};void bar( stuff = B );

Make bar compile.
spraff
spraff
Quote:
Original post by NotAYakk
I'm going to use the fish trick, rather than answering the last question.
struct foo {  enum stuff {A, B, C};};void bar( stuff = B );

Make bar compile.

Shouldn't that be "void bar (foo::stuff = foo::B)"? I don't see how that's relevant.

To recap:
template <int i, typename T>class Base {public:	enum Enum {A,B,C};};template <typename T>class Foo<0,T> : public Base<0,T> {public:	typedef typename Base<0,T>::Enum Enum;	void bar (Enum=B); // B is not defined.	// Here I had to refer to "typename Base<0,T>::Enum" to	// get the Enum symbol recognised. But how do I make B recognised?	// Surely Foo::B is defined since Foo::Enum is defined?};
NotAYakk
NotAYakk
Quote:
Original post by spraff
Quote:
Original post by NotAYakk
I'm going to use the fish trick, rather than answering the last question.
struct foo {  enum stuff {A, B, C};};void bar( stuff = B );

Make bar compile.

Shouldn't that be "void bar (foo::stuff = foo::B)"? I don't see how that's relevant.

To recap:
*** Source Snippet Removed ***


The line in error:
	void bar (Enum=B); // B is not defined.


My answer is:
Quote:
Shouldn't that be "void bar (foo::stuff = foo::B)"?

which you so nicely already provided! :)

In this case, foo is
	typedef typename Base<0,T>::Enum Enum;

Base<0,T>.
spraff
spraff
Okay, so the problem's solved, but I still don't understand it. Why do you only have to supply the classes own name with scope resolution when it's a template class? Viz:
class Bar {public:	enum Enum {A,B,C};};class Baz : public Bar {	void x (Enum=B); // No error here};template <int I, typename T>class Base {public:	enum Enum {A,B,C};};template <int I, typename T>class Foo : public Base<I,T> {};template <typename T>class Foo<0,T> : public Base<0,T> {public:	typedef typename Base<0,T>::Enum Enum;	void bar (Enum=B); // Error here	void baz (Enum=Foo::B); // This works};
spraff
spraff
Thanks for that link. It helps, but
Quote:
Here's the rule: the compiler does not look in dependent base classes (like B) when looking up nondependent names (like f).
Okay, but why?

Incidentally, I just discovered that this works:
template <int I, typename T>class Base {public:	enum Enum {A,B,C};};template <int I, typename T>class Foo : public Base<I,T> {};template <typename T>class Foo<0,T> : public Base<0,T> {public:	typedef typename Base<0,T>::Enum Enum;//	void bar (Enum=B); // Error here	void baz (Enum=Foo::B); // This works};class Tmp : public Foo<0,int> {public:	void f (Enum=B); // Ta-da!};
This is a huge relief, but it's still confusing. "Enum" and "C" are dependent in Tmp if they are dependent in Foo, surely!
NotAYakk
NotAYakk
Because they parse the template BEFORE they plug in the parameters.

And it looks for a global symbol. It cannot in general prove that the parent has any given structure.

When you say that it is from this class, you are saying 'it will be provided in the class', and the compiler trusts you.

The problem that is trying to be avoided is this:

typedef int foo;template<typename T>struct bar:T {  foo x;};template<typename T>struct nod:T {  foz x;};struct baz {  typedef double foo;  typedef double foz;};bar<baz> meh1;nod<baz> meh2;meh1.x is an intmeh2.x is a double

When the template parses the bar, it says "hey look, foo. I see foo! Ok, x is an int." When it parses nod, it says "I don't see a foz. So I'll wait and see if it appears!"

To fix this, foz in nod is an error, as where you wanted it from isn't defined within nod. foo remains the global symbol.

You fix this by saying where foz comes from in nod, and adorn it with typename to let the compiler that it is a type and not a static variable/enum/other r/l value.

A similar problem occurs with values, of course. So you have to say where you get it, IF it is a symbol whose lookup must be depenant on a template parameter.


Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.