6

The following code represents a container based on std::vector

template <typename Item>
struct TList
{
    typedef std::vector <Item> Type;
};


template <typename Item>
class List
{
private
            typename TList <Item>::Type items;
    ....
}

int main()
{
  List <Object> list;
}

Is it possible to templatize std::vector and create a general container, something like that?

template <typename Item, typename stl_container>
struct TList
{
    typedef stl_container<Item>;
};

where stl_container represents std::vector, std::list, std::set...? I would like to choose the type of container at the time of the creation.

List <Object, std::vector> list; //vector of objects, not a real code
List <Object, std::vector> list; //list of objects, not a real code

Thanks for your answers...

Updated question:

I tried the following code but there are errors:

#include <vector>
template <typename Item, typename Container>
struct TList
{
   typedef typename Container <Item>::type type; //Error C2059: syntax error : '<', Error C2238: unexpected token(s) preceding ';
};


template <typename T>
struct vector_container
{
  typedef std::vector<T> type;
};

int _tmain(int argc, _TCHAR* argv[])
{
TList <int, vector_container> v;
TList <int, map_container> m;
}
Robo
  • 311
  • 5
  • 10
  • Who would be using this class? Why can't `List` just use `typedef vector/list/set items`? What would be the purpose of a class that, given a container type and a value type, simply puts those two together? – UncleBens Feb 10 '11 at 21:03
  • Re: the edit, still don't see why you can't write `List > list;` etc – UncleBens Feb 10 '11 at 21:56

6 Answers6

11

Yes, but not directly:

template <typename Item, template <typename> class Container>
struct TList
{
    typedef typename Container<Item>::type type;
};

Then you can define different container policies:

template <typename T>
struct vector_container
{
    typedef std::vector<T> type;
};

template <typename T>
struct map_container
{
    typedef std::map<T, std::string> type;
};

TList<int, vector_container> v;
TList<int, map_container> m;

A bit verbose, though.* To do things directly, you'd need to take the route described by James, but as he notes this is ultimately very inflexible.

However, with C++0x we can do this just fine:

#include <map>
#include <vector>

template <typename Item,
            template <typename...> class Container, typename... Args> 
struct TList
{
    // Args lets the user specify additional explicit template arguments
    Container<Item, Args...> storage;
};

int main()
{
    TList<int, std::vector> v;
    TList<int, std::map, float> m;
}

Perfect. Unfortunately there's no way to reproduce this in C++03, except via the indirection policy classes introduce as described above.


*I want to emphasize that by "A bit verbose" I mean "this is unorthodox". The correct solution for your problem is what the standard library does, as Jerry explains. You just let the user of your container adapter specify the entire container type directly:

template <typename Item, typename Container = std::vector<Item>>
struct TList
{};

But this leaves a big problem: what if I don't want the value type of the container to be Item but something_else<Item>? In other words, how can I change the value type of an existing container to something else? In your case you don't, so read no further, but in the case we do, we want to rebind a container.

Unfortunately for us, the containers don't have this functionality, though allocators do:

template <typename T>
struct allocator
{
    template <typename U>
    struct rebind
    {
        typedef allocator<U> type;
    };

    // ...
};

This allows us to get an allocator<U> given an allocator<T>. How can we do the same for containers without this intrusive utility? In C++0x, it's easy:

template <typename T, typename Container>
struct rebind; // not defined

template <typename T, typename Container, typename... Args>
struct rebind<T, Container<Args...>>
{
    // assumes the rest are filled with defaults**
    typedef Container<T> type; 
};

Given std::vector<int>, we can perform rebind<float, std::vector<int>>::type, for example. Unlike the previous C++0x solution, this one can be emulated in C++03 with macros and iteration..


**Note this mechanism can be made much more powerful, like specifying which arguments to keep, which to rebind, which to rebind themselves before using as arguments, etc., but that's left as an exercise for the reader. :)

Community
  • 1
  • 1
GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • 1
    Good idea. Is there any problem that can't be solved with an extra layer of indirection? – James McNellis Feb 10 '11 at 21:09
  • @James: Nope. I can even see my forehead if I use a mirror. – GManNickG Feb 10 '11 at 21:09
  • Lol -- that's neat. And the comments are hilarious. +1. – Billy ONeal Feb 10 '11 at 21:10
  • For the line typedef typename Container ::Type type; compiler said: Error C2059: syntax error : '<', Error C2238: unexpected token(s) preceding ';' – Robo Feb 10 '11 at 22:27
  • @Robo: I don't have that line. (You have `::Type` while I have `::type`.) In any case, what code are you compiling? – GManNickG Feb 10 '11 at 22:49
  • @GMan> I placed your code in my question, see above, please. I thought ::type was an oversight... – Robo Feb 10 '11 at 23:05
  • @Robo: Oh yes, sorry, typo in my answer. Fixed. (Note your posted code in the question omits `map_container`, though.) (And @Downvoter: Comment? Can't fix things if I don't know what's wrong.) – GManNickG Feb 10 '11 at 23:09
  • @GMan. Thanks for your answer... I am sleepy and did not noticed your last comment :-) The first option is sufficient... Tomorrow I'll try and (possibly) get back. – Robo Feb 10 '11 at 23:24
  • 1
    And with template aliases you can write `template using rebind = typename detail::rebind::type;`. – Johannes Schaub - litb Feb 11 '11 at 17:13
  • I don't follow how you are going about things in the code block following the words "However, with C++0x we can do this just fine:". The obvious way of doing this is using template typedefs. That doesn't look like what you are doing, though. Is the feature that you can add arbitary trailing template arguments in C++0x? If so, can you add a reference, please? Thanks. – Faheem Mitha Dec 14 '11 at 20:00
  • Ah, I see. You are using [Variadic Templates](http://en.wikipedia.org/wiki/Variadic_templates) here, right? So this can be solved by either template typedefs or variadic templates, it looks like. Which are complementary operations, in a way. One lets you reduce the number of templates, the other lets you extend them. :-) – Faheem Mitha Dec 14 '11 at 20:09
6

I'm a bit puzzled why some very smart (and competent) people are saying no.

Unless I've misread your question, what you're trying to accomplish is virtually identical to the "container adapters" in the standard library. Each provides an interface to some underlying container type, with the container type that will be used provided as a template parameter (with a default value).

For example, a std::stack uses some other container (e.g., std::deque, std::list or std::vector) to hold the objects, and std::stack itself just provides a simplified/restricted interface for when you just want to use stack operations. The underlying container that will be used by the std::stack is provided as a template parameter. Here's how the code looks in the standard:

namespace std {
    template <class T, class Container = deque<T> >
    class stack {
    public:
        typedef typename Container::value_type value_type;
        typedef typename Container::size_type  size_type;
        typedef Container                      container_type;
    protected:
        Container c;
    public:
        explicit stack(const Container& = Container());
        bool empty() const             { return c.empty(); }
        size_type size() const         { return c.size(); }
        value_type& top()              { return c.back(); }
        const value_type& top() const  { return c.back(); }
        void push(const value_type& x) { c.push_back(x); }
        void pop()                     { c.pop_back(); }
    };
}

Of course, perhaps I've just misunderstood the question -- if so, I apologize in advance to the people with whom I'm (sort of) disagreeing.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Jerry: The OP wants code written as `MyClass` to be able to create a `std::vector`. In the `std::stack` example, the passed typename isn't `std::deque`, it's `std::deque` -- not a class template, but a template class. That is, your code would result in the OP having to do `MyClass >`. – Billy ONeal Feb 10 '11 at 21:22
  • @Billy O'Neal: I've reread the question, and still can't find where he says that's what he wants. Maybe I just didn't get enough sleep last night or something though.... – Jerry Coffin Feb 10 '11 at 21:25
  • I think the point of the question was to get the container without passing it's entire type into the template parameter. (For example, so I can make the value type `secret_type`.) But I think your answer is important too since we should solve problems, and not just answer questions, and this is how most of us would go about it. – GManNickG Feb 10 '11 at 21:26
  • @Jerry: See the OP's second codebox. (Look at the typedef there) – Billy ONeal Feb 10 '11 at 21:26
  • @GMan: That may be what he wants, but I can't find anything in the question that seems to say so. My impression was simply that he wanted something like a container adapter, allowing him to plug in a different underlying container when/if he sees fit. – Jerry Coffin Feb 10 '11 at 21:28
  • @Billy O'Neal: I've looked at it. I can't see where it does much to support your position though. – Jerry Coffin Feb 10 '11 at 21:29
  • @Jerry: In order to compile, the typename `stl_container` would need to be a template template (as in @James') answer. – Billy ONeal Feb 10 '11 at 21:32
  • I read the question the same way @GMan did. It's quite possible that I'm reading it wrong, though. This solution is, IMO, the most natural solution. – James McNellis Feb 10 '11 at 21:35
  • @Billy O'Neal: but even if it was a template template, it still wouldn't compile. A typedef has to look something like `typedef sometype somename;` What he has is (at best) just `typedef sometype;`, with no name for the typedef. I think far too much work is being put into fitting a very specific syntax, when all he seems (to me) to be doing is trying to portray the general idea that inside of his class, the type for the underlying container will be passed in as a parameter. – Jerry Coffin Feb 10 '11 at 21:40
  • @James McNellis: Well, I guess at this point it doesn't matter a lot. He now has answers that cover both possibilities (and, whether it's what the OP wanted or not, GMan's answer is pretty darned cool). – Jerry Coffin Feb 10 '11 at 21:47
5

Yes and no.

You can use a template template parameter, e.g.,

template <typename Item, template <typename> class Container>
struct TList { /* ... */ };

However, in general, template template parameters are not particularly useful because the number and types of the template parameters have to match. So, the above would not match std::vector because it actually has two template parameters: one for the value type and one for the allocator. A template template parameter can't take advantage of any default template arguments.

To be able to use the std::vector template as an argument, TList would have to be declared as:

template <typename Item, template <typename, typename> class Container>
struct TList { /* ... */ };

However, with this template, you wouldn't be able to use the std::map template as an argument because it has four template parameters: the key and value types, the allocator type, and the comparator type.

Usually it is much easier to avoid template template parameters because of this inflexibility.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • Could you replace `template class Container` with just `typename Container`, and access the type contained in the container using `Container::value_type`? – Billy ONeal Feb 10 '11 at 21:06
  • @Billy: Sure; every container has a `value_type` typedef. However, that won't allow you to _create_ the container, which is what the OP is asking about. – James McNellis Feb 10 '11 at 21:07
  • @James: Ah -- I see. Looks like the OP needs some iterators then :) – Billy ONeal Feb 10 '11 at 21:08
  • Sure, just look at what std::stack is doing! – Bo Persson Feb 10 '11 at 21:11
  • @Bo: Well, `std::stack` doesn't take a template, it takes a container type. That is, it doesn't take `std::deque`, it takes `std::deque`. – James McNellis Feb 10 '11 at 21:17
  • Since vector and map are decidedly different kinds of containers, which share very little in the way of use scope, the problem you're mentioning seems to me to be a practical non-issue. – Edward Strange Feb 10 '11 at 21:26
  • @Crazy: Well, I picked `std::map` because it appeared in the OP's list and has a template parameter list that doesn't match the `std::vector` template parameter list. It's still an issue though, even just for sequence containers: while all of the Standard Library sequence containers (except the non-container `array` in C++0x) have two type template parameters (`T` and `Alloc`), there is no requirement that other user-defined containers have exactly two type template parameters; a user-defined container might have more or less or different template parameters. – James McNellis Feb 10 '11 at 21:33
  • @Crazy: let's pick `std::set` then, not associative, and yet 3 parameters instead of the 2 of `std::vector`. – Matthieu M. Feb 11 '11 at 07:45
  • @Matthieu M. - I hope you don't actually think those two containers are interchangeable. – Edward Strange Feb 11 '11 at 17:17
2

well, you can hack it up with a macro:

template <typename T, typename stl_container = std::vector<T> >
struct TList
{
    typedef stl_container Type;
};

#define TLIST(T, C) TList<T, C<T> >

TList<int> foo;
TList<int, std::list<int> > bar;
TLIST(int, std::list) baz;
Tim
  • 8,912
  • 3
  • 39
  • 57
0

Is it possible to templatize std::vector and create a general container, something like that?

No. You would have to templatize the function or object using the container -- you couldn't templatize the container itself.

For example. consider a typical std::find:

template<class InputIterator, class T>
InputIterator find ( InputIterator first, InputIterator last, const T& value )
{
    for ( ;first!=last; first++) if ( *first==value ) break;
    return first;
}

This works for any container, but doesn't need a tempalte with the container at all.

Also, given that it looks what you're trying to do is make container independent code, you might want to buy or borrow yourself a copy of Scott Meyers' Effective STL and read Item 2: Beware the illusion of container-independent code.

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • Isn't algorithms taking iterators, rather than containers/ranges partly the cause of impossibility of container-independent code? It *is* possible to write a generic find function, that also works efficiently for set and map, only not with this signature. – UncleBens Feb 10 '11 at 21:11
  • @UncleBens: Yes -- the item in Effective STL is discouraging someone from trying to use only features all the STL containers provide, so that they could switch them out. (Meyers is better at explaining than I am) If you can templatize an algorithm by means of a template then by all means go for it. (But you'll probably need a few template specializations if you want it to be most efficient for both sequence and associative containers ;) ) – Billy ONeal Feb 10 '11 at 21:13
  • Regarding item 2: Beware the illusion of container-independent code, isn't most of the algorithms in STL under that spirit? – kirakun Feb 10 '11 at 21:15
  • @Kirakun: The algorithms are implemented in terms of iterators, not containers. – Billy ONeal Feb 10 '11 at 21:16
-1

You can use template template parameters as others have mentioned here. The main difficulty with this is not that dissimilar container types have dissimilar template parameters, but that the standard allows the standard containers, like vector, to have template parameters in addition to the documented, necessary ones.

You can get around this by providing your own subclass types that accept the appropriate template parameters and let any extras (which have to have defaults) be filled in my the implementation:

template < typename T > struct simple_vector : std::vector<T> {};

Or you can use the templated typedef idiom:

template < typename T > struct retrieve_vector { typedef std::vector<T> type; };
Edward Strange
  • 40,307
  • 7
  • 73
  • 125
  • 3
    [The C++ Standard does not allow an implementation to have additional, optional template parameters.](http://stackoverflow.com/questions/1469743/standard-library-containers-with-additional-optional-template-parameters) – James McNellis Feb 10 '11 at 21:59
  • I downvote because you inherit from standard containers. They do not have a virtual destructor, so it is dangerous to use your `simple_vector` class in places where you would expect a `vector`; users of the class could (and will) get it wrong and let `vector::~vector` be called instead of `simple_vector::~simple_vector`. – Alexandre C. Feb 10 '11 at 23:33
  • Not in this case Alexandre. Give yourself a moment or two to think about it. Or longer... – Edward Strange Feb 10 '11 at 23:42