Templated class template declarations

Started by
19 comments, last by Timkin 18 years, 4 months ago
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.
:stylin: "Make games, not war.""...if you're doing this to learn then just study a modern C++ compiler's implementation." -snk_kid
Advertisement
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
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."
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.
:stylin: "Make games, not war.""...if you're doing this to learn then just study a modern C++ compiler's implementation." -snk_kid
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.
:stylin: "Make games, not war.""...if you're doing this to learn then just study a modern C++ compiler's implementation." -snk_kid
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
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.
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?
@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]
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]

This topic is closed to new replies.

Advertisement