Template argument matching

I

Imre

Please take a look at the following code:

template <typename T, typename Enable = void>
class A
{
public:
enum { value = 0 };
};

template <typename T>
class A<T, typename T::Enable>
{
public:
enum { value = 1 };
};

class B
{
public:
typedef void Enable;
};

class C
{
};

int main(int argc, char *argv[])
{
int b = A<B>::value; // should be 1
int c = A<C>::value; // should be 0
return 0;
}

The goal here was to create a class template called A in a way that
A<T>::value is 1 if T::Enable exists, and 0 otherwise. I've tried this
on MSVC++ 7.1, and it actually works, but only if T::Enable is exactly
the same type as the default value of the Enable template argument of
A. For example, if I change the typedef in B to
typedef int Enable; // int instead of void
then the value of variable b in main will be 0.

I'd like to know why's that. I thought that the specialization would be
a better match for all types where T::Enable exists, regardless of what
type it actually means. And I can't really see the connection with the
second template argument.

Imre
 
S

Shezan Baig

Imre said:
Please take a look at the following code:

template <typename T, typename Enable = void>
class A
{
public:
enum { value = 0 };
};

template <typename T>
class A<T, typename T::Enable>
{
public:
enum { value = 1 };
};

class B
{
public:
typedef void Enable;
};

class C
{
};

int main(int argc, char *argv[])
{
int b = A<B>::value; // should be 1
int c = A<C>::value; // should be 0
return 0;
}

The goal here was to create a class template called A in a way that
A<T>::value is 1 if T::Enable exists, and 0 otherwise. I've tried this
on MSVC++ 7.1, and it actually works, but only if T::Enable is exactly
the same type as the default value of the Enable template argument of
A. For example, if I change the typedef in B to
typedef int Enable; // int instead of void
then the value of variable b in main will be 0.

I'd like to know why's that. I thought that the specialization would be
a better match for all types where T::Enable exists, regardless of what
type it actually means. And I can't really see the connection with the
second template argument.

Imre

I'm guessing what you really want is a "trait":

.. #include <iostream>
..
.. template <typename T>
.. class Trait
.. {
.. public:
.. enum { ENABLE = 0 };
.. };

This defines a trait, by default it is zero. Now you can define your
'A' class like this:

.. template <typename T, typename T2 = Trait<T> >
.. class A
.. {
.. public:
.. enum { value = T2::ENABLE };
.. };

This gets 'value' from the 'ENABLE' enum in your Trait class (which is
zero by default). Now you can create your other classes:

.. class B { };
.. class C { };

Now, specialise Trait<T> so that 'ENABLE' is set to one for class 'B':

.. template <>
.. class Trait<B>
.. {
.. public:
.. enum { ENABLE = 1 };
.. };

This means that 'B' is now enabled for whatever you want to do in 'A'.

.. int main(int argc, char *argv[])
.. {
.. int b = A<B>::value; // should be 1
.. int c = A<C>::value; // should be 0
..
.. std::cout << b << " " << c << std::endl;
..
.. return 0;
.. }
This should print out "1 0" (as expected).

Hope this helps,
-shez-
 
R

rixil

I think it is possible that you are confusing default template
parameters with with partial template specialization.

If you look at your code above, neither of the lines:
int b = A<B>::value; // should be 1
int c = A<C>::value; // should be 0

explicitly supplies the 'Enable' parameter of your original template.

For that reason, you have implicitly written:

int b = A<B, void>::value;
int c = A<C, void>::value;

In other words, in both cases, your second template parameter is of
type void.

This means that when the compiler is deciding whether or not to use the
partial template specialization:
template <typename T>
class A<T, typename T::Enable>
{
public:
enum { value = 1 };
};

that T::Enable must be of type void in order to the specialization to
be chosen.

If you write:

class B
{
public:
typedef int Enable;
};

then the implicitly written line:

int b = A<B, void>::value;

does not use the partial template specialization:
template <typename T>
class A<T, typename T::Enable>
{
public:
enum { value = 1 };
};

because T::Enable (really B::Enable) is of type int, and the second
parameter of A<B, void> is of type void. In other words, since void is
the type of the second template parameter, a T::Enable of type int
cannot be applied, and the specialization cannot be chosen because of
the type conflict.

I hope this helps! =)

Michael Loritsch
 
S

Shezan Baig

Shezan said:
This defines a trait, by default it is zero. Now you can define your
'A' class like this:

. template <typename T, typename T2 = Trait<T> >
. class A
. {
. public:
. enum { value = T2::ENABLE };
. };

Also, if you want to have a separate specialisation for ENABLE = 1 (as
in your original post), you can do this:

.. template <typename T,
.. typename T2 = Trait<T>,
.. int ENABLE = T2::ENABLE >
.. class A
.. {
.. public:
.. enum { value = 0 };
.. };
..
.. template <typename T, typename T2>
.. class A<T, T2, 1>
.. {
.. public:
.. enum { value = 1 };
.. };

This will give you the compile-time polymorphism I think you were
looking for in your original post.

Hope this helps,
-shez-
 
I

Imre

I think it is possible that you are confusing default template
parameters with with partial template specialization.

If you look at your code above, neither of the lines:


explicitly supplies the 'Enable' parameter of your original template.

For that reason, you have implicitly written:

int b = A<B, void>::value;
int c = A<C, void>::value;

In other words, in both cases, your second template parameter is of
type void.

Ah, I see. From this point on, everything makes perfect sense. Somehow
I thought that the compiler would first look for specializations
(before binding the default value to the second argument), and then,
having found the specialization, deduce the second argument instead of
using the default one. Well, I was wrong.

So it seems that I'll have to provide some more information about why I
originally started to play with this.

I plan to use some hierarchy-wide traits, as presented by A.
Alexandrescu at
http://www.moderncppdesign.com/publications/traits_on_steroids.html.

Basically it looks like this:

template <class HierarchyId>
class HierarchyTraits
{
... most general traits here ...
};
template <class T>
class Traits
: public HierarchyTraits<T::HierarchyId>
{
// empty body - inherits all symbols from base class
};

Now if a root class of a class hierarchy has a nested class called
HierarchyId, you can specialize HierarchyTraits for Root::HierarchyId,
which is in effect the same as if you'd specialize Traits for all
descendants of Root. You can, of course, also directly specialize
Traits for specific types.

My problem with this solution is that the default implementation of
Traits explicitly references T::HierarchyId, thus cannot be used for
types that don't have the nested class HierarchyId. And that's exactly
what I would need. I'd like to have a default implementation that
doesn't need HierarchyId, and then have a specialization that should be
used for all types that do have HierarchyId. Only the specialization
would inherit from HierarchyTraits<T::HierarchyId>. The code in my
original post was an attempt to do this, but as you can see, I just
don't know enough about templates to get it right.

So how can it be done?

And thanks for all the answers so far.

Imre
 
S

Shezan Baig

Imre said:
My problem with this solution is that the default implementation of
Traits explicitly references T::HierarchyId, thus cannot be used for
types that don't have the nested class HierarchyId. And that's exactly
what I would need. I'd like to have a default implementation that
doesn't need HierarchyId, and then have a specialization that should be
used for all types that do have HierarchyId. Only the specialization
would inherit from HierarchyTraits<T::HierarchyId>. The code in my
original post was an attempt to do this, but as you can see, I just
don't know enough about templates to get it right.

So how can it be done?

The solution I posted earlier does something like this. It has a
default trait, and you can specialise the trait as your wish for
whatever class you want.

Hope this helps,
-shez-
 
S

Shezan Baig

Shezan said:
Ok, but how do I specialize it for a whole (possibly big) class
hierarchy at once? I don't want to create a separate specialization for
all subclasses. There's a lot of them, and all these specializations
would be exactly the same.

Ahh, I see. Sorry, I misread your earlier post. I'm not aware of how
we can generalise this. The best I can come up with now is to use a
macro:

.. #define ENABLE_TRAIT(T) \
.. template <> \
.. class Trait<B> \
.. { \
.. public: \
.. enum { ENABLE = 1 }; \
.. }; \

Now you just need to do:

ENABLE_TRAIT(B)
ENABLE_TRAIT(SomeClassThatDerivesFromB)

This is the best I can think of now. It's only one line per class, so
I don't think it will be *that* bad :)

Hope this helps,
-shez-
 
S

Shezan Baig

Shezan said:
Ahh, I see. Sorry, I misread your earlier post. I'm not aware of how
we can generalise this. The best I can come up with now is to use a
macro:

. #define ENABLE_TRAIT(T) \
. template <> \
. class Trait<B> \


This should, of course, read:

.. class Trait<T>
 
I

Imre

My problem with this solution is that the default implementation of
Traits explicitly references T::HierarchyId, thus cannot be used for
types that don't have the nested class HierarchyId. And that's exactly
what I would need. I'd like to have a default implementation that
doesn't need HierarchyId, and then have a specialization that should be
used for all types that do have HierarchyId. Only the specialization
would inherit from HierarchyTraits<T::HierarchyId>. The code in my
original post was an attempt to do this, but as you can see, I just
don't know enough about templates to get it right.

So how can it be done?

Well, okay, it's actually not that difficult. My problem was that I
wanted to specialize Traits for every types where T::HierarchyId
exists, whatever HierarchyId really is. Unfortunately, whatever is not
that easy to match. The solution is simply to put a typedef inside the
nested HierarchyId class, and specialize Traits based on the type value
of that typedef, insted of HierarchyId itself.

Imre
 
S

Shezan Baig

Imre said:
should

Well, okay, it's actually not that difficult. My problem was that I
wanted to specialize Traits for every types where T::HierarchyId
exists, whatever HierarchyId really is. Unfortunately, whatever is not
that easy to match. The solution is simply to put a typedef inside the
nested HierarchyId class, and specialize Traits based on the type value
of that typedef, insted of HierarchyId itself.

Imre

Hi Imre,

I'm not sure I quite follow you. Can you post some code to demonstrate
this?

Thanks!

-shez-
 
I

Imre

I'm not sure I quite follow you. Can you post some code to
demonstrate

Sure.

template <typename T, typename Enable = void>
struct Traits
{
enum { value = 0 };
};

template <typename HierarchyId>
struct HierarchyTraits
{
};

template <typename T>
struct Traits<T, typename T::HierarchyId::Exists>:
public HierarchyTraits<typename T::HierarchyId>
{
};

/*
Now let's create a class and specialize Traits for it and all its
subclasses:
*/

class B
{
public:
class HierarchyId
{
typedef void Exists;
};
};

template <>
struct HierarchyTraits<B::HierarchyId>
{
enum { value = 1 };
};

/*
Now some subclasses of B. D1 defines its own subtree inside the class
hierarchy tree, with a different specialization of Traits. D2 just
uses B's version.
*/

class D1: public B
{
public:
class HierarchyId
{
typedef void Exists;
};
};

template <>
struct HierarchyTraits<D1::HierarchyId>
{
enum { value = 2 };
};

class D2: public B
{
};

/*
Let's also have a class that is not part of any hierarchies.
*/

class C
{
};

int main(int argc, char *argv[])
{
int b = Traits<B>::value;
int c = Traits<C>::value;
int d1 = Traits<D1>::value;
int d2 = Traits<D2>::value;
int i = Traits<int>::value;
return 0;
}

b and d2 should be 1 (value inherited from the HierarchyTraits
specialization we created for B::HierarchyId), d2 is 2 (D2's own
hierarchy specialization), c and i should be 0 (default, cause they
have no HierarchyId).

Actually that was all I wanted: to use hierarchy-wide traits while also
providing some defaults for types that are not part of any defined
hierarchies.

Imre
 
S

Shezan Baig

What compiler are you using? I am getting the following errors (using
Sun CC):


"test_heirarchytraits.cpp", line 18: Error: HierarchyId is not a member
of C.
"test_heirarchytraits.cpp", line 85: Where: While specializing
"Traits<C, void>".
"test_heirarchytraits.cpp", line 85: Where: Specialized in
non-template code.
"test_heirarchytraits.cpp", line 85: Error: value is not a member of
Traits<C, void>.
"test_heirarchytraits.cpp", line 18: Error: T is not a namespace or
class name.
"test_heirarchytraits.cpp", line 88: Where: While specializing
"Traits<int, void>".
"test_heirarchytraits.cpp", line 88: Where: Specialized in
non-template code.
"test_heirarchytraits.cpp", line 18: Error: HierarchyId is not defined.
"test_heirarchytraits.cpp", line 88: Where: While specializing
"Traits<int, void>".
"test_heirarchytraits.cpp", line 88: Where: Specialized in
non-template code.
"test_heirarchytraits.cpp", line 88: Error: value is not a member of
Traits<int, void>.
5 Error(s) detected.
 
I

Imre

Shezan said:
What compiler are you using? I am getting the following errors (using
Sun CC):

I've originally written it using MSVC++ 7.1.
Now I also tried it with gcc 3.4.3. Gcc didn't compile it at first, but
only because the Exists typedefs were private inside the HierarchyId
classes. After I've added public:, it compiled and worked well.
 
S

Shezan Baig

Imre said:
I've originally written it using MSVC++ 7.1.
Now I also tried it with gcc 3.4.3. Gcc didn't compile it at first, but
only because the Exists typedefs were private inside the HierarchyId
classes. After I've added public:, it compiled and worked well.

Cool. I haven't got that version on me right now, but thanks!

-shez-
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,201
Messages
2,571,048
Members
47,647
Latest member
NelleMacy9

Latest Threads

Top