Sign in to follow this  
stormrunner

Templated class template declarations

Recommended Posts

I read somewhere - most likely C++ Templates - that templated classes can be used/written/declared in the template declaration itself. I.e. they could be declared with their own independent types that are separate from the rest of the template declaration. Unfortunately, I've forgotten both how to do it and what such a thing is called (thus Google hasn't been helpful). Here's a small (and incorrect) example to demonstrate what I mean :
// this compiles fine
template<typename T, template<typename T2> class C>
class Foo { /* */ };

// as does this ... for obvious reasons
template<typename T>
class Bar { /* */ };

// unfortunately, this spawns an error in VS8
// error C3200: 'Bar<T>' : invalid template argument for template parameter 'C',
//     expected a class template
Foo< int, Bar<int> > foo;


What am I missing ? Any help would be greatly appreciated. <edit :: fixed source tags

Share this post


Link to post
Share on other sites
Bar<int> isn't a template, it's an instantiation of a template. Change that last line to
Foo< int, Bar > foo;
and it should compile.

Share this post


Link to post
Share on other sites
Thank you, both of you, but I'm still a bit confused. In the line
Foo<int, Bar>

shouldn't it be specified exactly what type Bar should be ?

In the same vein, say I have the aforementioned declarations, except Foo now has a member variable Bar<T>. If I try to assign it to something ( a concrete Bar<T> object ) it won't compile
template< typename T, template< typename T2 > class C >
class Foo
{
private:
C _foobar;
public:
Foo( C foobar )
: _foobar( foobar )
{ }
};

// use it ...
Bar<int> bar;
Foo< int, Bar > foo( bar );


VS8 informs me that "the argument list for template template parameter 'C' is missing", along with a hefty amount of excessive gibberish.

Share this post


Link to post
Share on other sites
In Foo Bar is a template template parameter so you need to instantiate the template. ex:

template< typename T, template< typename T2 > class C >
class Foo
{
private:
C<T> _foobar;
public:
Foo( C<T> foobar )
: _foobar( foobar )
{ }
};

Share this post


Link to post
Share on other sites
Thanks again, SiCrane, that solved my problem and cleared up the main point I was confused on. However, is there no way to simply pass the type with the declaration ? In my original example, I meant for C<T2> to be independant of T. So I could have something like Foo<int, Bar<std::string> >.

Share this post


Link to post
Share on other sites
Sure just use a normal type argument.

template <typename T1, typename T2>
class Foo {
// blah blah blah
};

Share this post


Link to post
Share on other sites
Quote:
Original post by stormrunner
Thanks again, SiCrane, that solved my problem and cleared up the main point I was confused on. However, is there no way to simply pass the type with the declaration ? In my original example, I meant for C<T2> to be independant of T. So I could have something like Foo<int, Bar<std::string> >.


template< typename T, template< typename > class C, typename T2 >
class Foo { ... };

...

Foo< int, Bar, std::string> foobar;

That's somewhat of a pain, though.

Share this post


Link to post
Share on other sites
template <typename c1, typename c2> class chicken
{
};

template <typename c1> class zebra
{
};

// in main
chicken<int, zebra<std::string> > my_chicken;

Should work dandily.

Share this post


Link to post
Share on other sites
whats wrong with ...

template < typename T, template < typename T2 > typename C<T2> >
class Foo{...};


template <typename T>
class Bar{...};


Foo< int, Bar<std::string> >


?

Share this post


Link to post
Share on other sites
Quote:
Original post by Timkin
whats wrong with ...

template < typename T, template < typename T2 > typename C<T2> >
class Foo{...};


template <typename T>
class Bar{...};


Foo< int, Bar<std::string> >


?

Doesn't compile for me, since 1) you're using typename instead of class (class is required for template template parameters) and 2) see Urxae's explaination above. Also, the initial T2 is merely an optional argument name - it's not used (just like in function declarations).

You can either specify T2 as a seperate template parameter, or just Bar< T2 > - which contains a typedef for T2 if you need that information.

Share this post


Link to post
Share on other sites
Quote:
Original post by stylin
Doesn't compile for me, since 1) you're using typename instead of class (class is required for template template parameters)


Triviality aside...

Quote:

You can either specify T2 as a seperate template parameter, or just Bar< T2 > - which contains a typedef for T2 if you need that information.

So you're suggesting that
template < typename T, typename Bar < T2 > > class Foo{...};

is the correct way to go if you want to know (within Foo) what type Bar< > is instantiated with? Why? How does this differ (at a template and instantiation level) from
template <typename T, template < typename T2 > class Bar<T2> > class Foo{...};
, from which I clearly know that Bar is a template class and that it is instantiated with a type aliased by T2. Certainly with

template <typename T2> class Bar{...};
template <typename T, typename B> class Foo{...};
Foo<int, Bar<std::string> > foo;

I have no reason (from within Foo) to believe that Bar is a templated type. But if I have
template < typename T, template <typename T2 > class Bar < T2 > > class Foo{...};
then I know that my second template parameter is a templated type and that it is instantiated on a type T2. Presumably if I'm passing templated types, I care that they're templates and not just regular types (because I'm probably going to use Bar objects in a context where I need to know the type upon which they're instantiated).

I'm asking this because I'm truly trying to understand the intricacies of templating better... if I'm wrong (or suggesting a poor usage), please point out why and explain.

Thanks,

Timkin

Share this post


Link to post
Share on other sites
Well there are a few objections I can think of; how important each of them are depend on you, your team and your project.

1) It's not too portable. This is nowhere as big a deal as it was even a year ago, but there are still a depressingly large number of compilers in use that don't grok that syntax.

2) The number of programmers that grok that syntax aren't that large either. The syntax template <typename T1, typename T2> is instantly accessible to most C++ programmers. The syntax template <typename T, template < typename T2 > class Bar<T2> > is less easily comprehended. It also tends to create more confusing error messages than template <typename T1, typename T2>, and when working with template code you really need all the help you can get when dealing with errors.

3) You are now stuck using template classes with a single template argument. You can't instantiate the class with std::vector<int> since std::vector<int> has two template arguments.

4) It goes against idiom. For instance, consider std::list<>. std::list<>'s second template argument is an allocator that uses rebind to designate alternate allocators that are actually used. Admittedly, the standard library isn't always the best source of design ideas, but this is one that every C++ programmer isshould be familiar with.

5) Talking about the class becomes, to use the technical term, just plain sucky. "So the class takes two template arguments. The first argument is a type parameter. The second template argument is a template template parameter parameterized on on single type parameter with a named type argument. So in order to instantiate the class you need to pass a template type instantiated with a type as the second template argument. The type used as the template argument is used as the default value." "The template argument for the whole template, or the template template parameter?" "Urgh. I need a whiteboard now."

Share this post


Link to post
Share on other sites
Quote:
Original post by Timkin
So you're suggesting that
template < typename T, typename Bar < T2 > > class Foo{...};

is the correct way to go if you want to know (within Foo) what type Bar< > is instantiated with? Why?

It's one of the ways to gain access to that information. I mentioned it because it was already suggested twice, and it probably - unless there is some problem with embedding a typedef in the passed template - is the easiest solution:

template< typename T >
struct templateHavingType {
public:
typedef T someType;
};

template< typename T1, typename T2 >
struct templateAcceptingTemplate {
T1 someData;
T2 someTemplate;
void useT2sInstatiatedType() {
typename T2::someType someT2BasedData;
// ...
}
};

int main() {
templateAcceptingTemplate< int, templateHavingType< float > > foobar;
foobar.useT2sInstatiatedType();
return 0;
}





Quote:
How does this differ (at a template and instantiation level) from
template <typename T, template < typename T2 > class Bar<T2> > class Foo{...};
, from which I clearly know that Bar is a template class and that it is instantiated with a type aliased by T2.

The syntax isn't legal:

template< typename T, template < typename T2 > class Bar< T2 > > class Foo{...};

The T2 in this case is simply a placeholder; it's only there to for the sake of the humans reading the code. The following two declarations are identical:

   template< typename T1, template< typename T2 > class C > struct S1;
template< typename T1, template< typename > class C > struct S2;


Since you're using a template, the compiler only needs to know that the template takes 1 argument. It doesn't care what that is or how it affects other arguments, therefore other arguments can't be dependent on it.

Quote:
Certainly with

template <typename T2> class Bar{...};
template <typename T, typename B> class Foo{...};
Foo<int, Bar<std::string> > foo;

I have no reason (from within Foo) to believe that Bar is a templated type.

You're right, you don't. But placing a specialized typedef usage inside of Foo will force the use of - at least a compatible - template argument. When you refactor to static polymorphism from dynamic polymorphism this is exactly what you're doing - you force an implementation on objects that are passed in. Objects that do not meet that criteria will halt the compiler. It's the difference between an assumption and a mandate.

Quote:
But if I have
template < typename T, template <typename T2 > class Bar < T2 > > class Foo{...};
then I know that my second template parameter is a templated type and that it is instantiated on a type T2. Presumably if I'm passing templated types, I care that they're templates and not just regular types (because I'm probably going to use Bar objects in a context where I need to know the type upon which they're instantiated).

Yes, and you get the same guarantee with an embedded typedef, since only objects with that typedef will allow the code to compile.

The other alternative I suggested was to explicitly pass T2 when instantiating Foo, like:

Foo< int, Bar, std::string > foo;

But that's really a non-solution that breaks encapsulation - it's also highly error-prone.

Quote:
I'm asking this because I'm truly trying to understand the intricacies of templating better... if I'm wrong (or suggesting a poor usage), please point out why and explain.

Sorry if I came off a little brash earlier - didn't mean to; I was rushed in typing my response. I'm also trying to understand templates better.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
1) It's not too portable. This is nowhere as big a deal as it was even a year ago, but there are still a depressingly large number of compilers in use that don't grok that syntax.

VC++ 8 doesn't, that's why I assumed it was invalid. Thanks for the clarification.

Share this post


Link to post
Share on other sites
Thanks for the answers guys, they've helped me to look at templating with a little more honest eye (and reassess some of the ways I've been using it recently). SiCrane's reason #4 seems to be the biggest kicker imo against using template template parameters (unless you absolutely know that you want to restrict the instantiability of your templates).

My only concern stems from the need to know the types that template parameters are instantiated on, if they themselves were template classes. What's the 'correct' (read that as 'most widely acceptable' please) way of handling this situation without limiting the template parameter types (or number of template parameters of the template parameter)? Does this make sense?

Cheers,

Timkin

Share this post


Link to post
Share on other sites
The C++ standard library container classes provide a typedef for the type the container was instantiated with as value_type. If reason 4 was the biggest kicker for you, you may want to consider requiring your template type parameters to have similar typedefs.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
If reason 4 was the biggest kicker for you, you may want to consider requiring your template type parameters to have similar typedefs.


Yep, I'm familiar with this usage for single types... but how would you handle template types that are themselves instantiated on more than 1 type. So just using Bar::value_type inside Foo would not suffice if Bar was Bar<T1,T2>. What is the usual way of handling this? Whats the idiom: value_type1... value_typeN?

Share this post


Link to post
Share on other sites
@Timkin except for trivial cases like maybe for policy classes i wouldn't bother with template template parameters as they are rigid, there are two other ways to achieve the same behaviour with more flexibility:

1. The old rebind trick that the standard library containers use:


template < typename Tp >
struct foo {
template < typename Tq >
struct rebind { typedef foo<Tq> other; } ;
};

template < typename Tp, typename Foo = foo<Tp> >
struct bar {

typedef typename Foo::template rebind<Tp>::other foo_type;

};


2. Even more flexible using boost mpl's placeholders & apply metafunction:


#include <memory>
#include <deque>
#include <boost/mpl/placeholders.hpp>
#include <boost/mpl/apply.hpp>

namespace foo {

using namespace boost::mpl::placeholders;

template <
typename Tp,
typename Container = std::deque<_1, _2>,
typename Alloc = std::allocator<Tp>
>
struct bar {
typedef Tp value_type;
typedef typename Alloc::template rebind<value_type>::other
allocator_type;
typedef typename boost::mpl::apply< Container, value_type, allocator_type >::type
container_type;
};
};

#include <deque>
#include <set>
#include <hash_set> // VC++ 8.0
#include <list>

int main() {

using namespace boost::mpl::placeholders;

typedef foo::bar<int> bar1_type;

typedef foo::bar<
short,
std::list<_1, _2>
> bar2_type;

typedef foo::bar<
double,
std::set<_1, std::less<double>, _2>
> bar3_type;

typedef foo::bar<
float,
stdext::hash_set<_1, stdext::hash_compare<float>, _2>
> bar4_type;
}


You just need to make sure you have the correct number of placeholders and in the correct place expected.

[Edited by - snk_kid on December 7, 2005 3:55:03 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by snk_kid
boost mpl's placeholders & apply metafunction


Heh, thanks, I can finally see the light at the end of the MPL tunnel. [smile]

Share this post


Link to post
Share on other sites
Quote:
Original post by snk_kid
@Timkin except for trivial cases like maybe for policy classes i wouldn't bother with template template parameters as they are rigid, there are two other ways to achieve the same behaviour with more flexibility:
[ snip ]


Thank you very much for the info, 'tis very much appreciated! 8)

Share this post


Link to post
Share on other sites

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