views:

297

answers:

3

Example, I want to specialize a class to have a member variable that is an stl container, say a vector or a list, so I need something like:

template <class CollectionType, class ItemType>
class Test
{
public:
    CollectionType<ItemType> m_collection;
};

So I can do:

Test  t = Test<vector, int>();
t.m_collection<vector<int>> = vector<int>();

But this generates

test.cpp:12: error: `CollectionType' is not a template
+12  A: 

What you want is a template template parameter:

template <template <typename> class CollectionType, class ItemType>
class Test
{
public:
    CollectionType<ItemType> m_collection;
};

What we did here is specifying that the first template parameter, i.e. CollectionType, is a type template. Therefore, Test can only be instantiated with a type that is itself a template.

However, as @Binary Worrier pointed in the comments, this won't work with STL containers since they have 2 template parameters: one for the elements type, the other one for the type of the allocator used for managing storage allocation (which has a default value).

Consequently, you need to change the first template parameter so that it has two parameters:

template <template <typename,typename> class CollectionType, class ItemType>
class Test
{
public:
    CollectionType<ItemType> m_collection;
};

But wait, that won't work either! Indeed, CollectionType awaits another parameter, the allocator... So now you have two solutions:

1 . Enforce the use of a particular allocator:

CollectionType<ItemType, std::allocator<ItemType> > m_collection

2 . Add a template parameter for the allocator to your class:

template <template <typename,typename> class CollectionType, 
          class ItemType,
          class Allocator = std::allocator<ItemType> >
class Test
{
public:
    CollectionType<ItemType, Allocator> m_collection;
};

So as you see, you end up with something rather complicated, which seems really twisted to deal with STL containers...

My advice: see Greg Rogers' answer for a better approach :)!

Luc Touraille
That works, I don't quite understand it. What is the <class T> part doing?
justinhj
This wouldn't compile for me (VS 2008), I'd to add a second type for the allocator on vector, making the template declaration be "template <template <typename, typename> class CollectionType, class ItemType>". The declaration for m_collection then became "CollectionType<ItemType, std::allocator<ItemType>>" +1 for pointing out "template template parameters", thanks :)
Binary Worrier
For an excellent tutorial on template template parameters see "http://www.informit.com/articles/article.aspx?p=376878"
Binary Worrier
@BinaryWorrier, yeah I get the same error when I instantiate a Test.
justinhj
@Binary Worrier You're completely right, that's what you get when you write an answer "quick and dirty" :-)! I'll change that right away.
Luc Touraille
This won't work if the implementation decides to add other parameters to std::vector. That's perfectly allowed, as long as all those have default argument. But it will break your code.
Johannes Schaub - litb
@litb, you're correct, that's one more argument in support of Greg Rogers' answer!
Luc Touraille
Hello again - I'm sorry i was wrong with that, back then! Many people say that this is allowed, but today i started to look for a *proof* in the Standard, and found that the Standard does *not* allow implementations to add additional, defaulted, parameters. See also this discussion within the committee: http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#94 . Upvoted! :)
Johannes Schaub - litb
Thanks a lot @litb, it is nice to see that you keep improving even old answers.
Luc Touraille
+9  A: 

Why not do it like this?

template <class CollectionType>
class Test
{
public:
    CollectionType m_collection;
};

Test  t = Test<vector<int> >();
t.m_collection = vector<int>();

If you need the itemtype you can use CollectionType::value_type.

EDIT: in response to your question about creating a member function returning the value_type, you do it like this:

typename CollectionType::value_type foo();

You add the typename because CollectionType has not been bound to an actual type yet. So there isn't a value_type it could look up.

Greg Rogers
Good answer: this way Test is more general since it can work even with non-templated container.
Luc Touraille
I'm not sure I can do that since I need to write a member function that returns an object of the Item type. I can't do CollectionType::value_type * advance() for example.
justinhj
@justinhj: Why not? Why can't you use CollectionType::value_type?
jalf
CollectionType::value_type test2Get();gives test3.cpp:30: error: expected `;' before "test2Get"
justinhj
From the STL docs it looks like value_type should be called on an iterator rather than a collection?
justinhj
containers are required to define several member types, such as reference, size_type, and value_type. See http://www.cplusplus.com/reference/stl/vector/ at the bottom, and the requirements are in section 23.1 "Container requirements" of the standard
Greg Rogers
You need to add typename before CollectionType::value_type. That's a common mistake when dealing with template members.
Luc Touraille
+2  A: 

Comeau online likes this:

#include <vector>

template <template <class T, class A = std::allocator<T> > class CollectionType, class ItemType>
class Test
{
public:
    CollectionType<ItemType> m_collection;
};

void foo()
{
using std::vector;
Test<vector,int>  t = Test<vector, int>();
t.m_collection = vector<int>();
}
tragomaskhalos