Sign in to follow this  

Quick templates question

This topic is 3197 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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...

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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<T>::foo.

Child inherits from Base<T>. I do a typedef of Base<T> 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.

Share this post


Link to post
Share on other sites
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<foo>::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? :-)

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?
};

Share this post


Link to post
Share on other sites
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>.

Share this post


Link to post
Share on other sites
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
};

Share this post


Link to post
Share on other sites
Thanks for that link. It helps, but
Quote:
Here's the rule: the compiler does not look in dependent base classes (like B<T>) 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!

Share this post


Link to post
Share on other sites
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 int
meh2.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.


Share this post


Link to post
Share on other sites

This topic is 3197 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this