5

Suppose, I've a STL container type (not object), say vector<A>. Now it's value_type is A, so I want to change it to B.

Basically, I want a class template of this form, or a variant of it:

template<typename container, typename new_value_type>
struct change_value_type
{
    typedef /*....*/  new_container;
};

So that I can use it in the following way:

typename change_value_type<vector<A>, B>::new_container  vectorOfB; 
vectorOfB.push_back(B());
vectorOfB.push_back(B());
vectorOfB.push_back(B());
//etc

Means, new_container is vector<B>

Is it possible?

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • What if it's in the form `set_value_type`? – kennytm Feb 19 '11 at 17:10
  • @KennyTM: No, that cannot do what I'm trying to do. I can work with that form. the problem comes when I use `vector` with a `type`, like `vector`. – Nawaz Feb 19 '11 at 17:12
  • 2
    I have touched on this already: http://stackoverflow.com/questions/4962518/templates-and-stl/4962708#4962708 – GManNickG Feb 19 '11 at 20:50

3 Answers3

6

Just stumbled over this as I was trying to essentially solve the same problem. It can even be made to work without relying on the rebind type that is specific to std::allocator – the only requirement is that the rebound value type is the first template parameter of the respective classes. This is the case for all relevant STL classes (std::vector, std::set, std::list etc. as well as for example std::less and std::allocator).

A pre-C++11 solution would look like this:

template <class Container, class NewType>
struct rebind;

template <class ValueType, template <class> class Container, class NewType>
struct rebind<Container<ValueType>, NewType>
{
  typedef Container<NewType> type;
};

template <class ValueType, class A, template <class, class> class Container, class NewType>
struct rebind<Container<ValueType, A>, NewType>
{
  typedef Container<NewType, typename rebind<A, ValueType>::type> type;
};

template <class ValueType, class A, class B, template <class, class, class> class Container, class NewType>
struct rebind<Container<ValueType, A, B>, NewType>
{
  typedef Container<NewType, typename rebind<A, ValueType>::type, typename rebind<B, ValueType>::type> type;
};

// Continue for more parameters (A, B, C, ...)

C++11 makes it a bit easier:

template <class Container, class NewType>
struct rebind;

template <class ValueType, class... Args, template <class...> class Container, class NewType>
struct rebind<Container<ValueType, Args...>, NewType>
{
  typedef Container<NewType, typename rebind<Args, NewType>::type...> type;
};

In order to support std::array, the following can be added:

template <class ValueType, std::size_t N, template <class, std::size_t> class Container, class NewType>
struct rebind<Container<ValueType, N>, NewType>
{
  typedef Container<NewType, N> type;
};

The result can be used with just about any STL type:

#include <iostream>
#include <typeinfo>
#include <vector>
#include <set>
#include <deque>
#include <queue>
#include <list>
#include <array>

#include "rebind.h"

// Make it all a bit more compact
#define REBIND_DEMO(container, new_type)                \
  do {                                                  \
    container test;                                     \
    rebind<decltype(test), new_type>::type test2;       \
    std::cout << typeid(test).name() << "\n";           \
    std::cout << typeid(test2).name() << "\n";          \
  } while (0)

int main()
{
  REBIND_DEMO(std::set<float>, double);
  REBIND_DEMO(std::list<float>, double);
  REBIND_DEMO(std::deque<float>, double);
  REBIND_DEMO(std::queue<float>, double);
  typedef std::array<float, 4> TestArray;
  REBIND_DEMO(TestArray, double);
  REBIND_DEMO(std::unordered_set<float>, double);

  return 0;
}

Running this and piping the output through c++filt -t on a Linux system gives you

std::set<float, std::less<float>, std::allocator<float> >
std::set<double, std::less<double>, std::allocator<double> >
std::list<float, std::allocator<float> >
std::list<double, std::allocator<double> >
std::deque<float, std::allocator<float> >
std::deque<double, std::allocator<double> >
std::queue<float, std::deque<float, std::allocator<float> > >
std::queue<double, std::deque<double, std::allocator<double> > >
std::array<float, 4ul>
std::array<double, 4ul>
std::unordered_set<float, std::hash<float>, std::equal_to<float>, std::allocator<float> >
std::unordered_set<double, std::hash<double>, std::equal_to<double>, std::allocator<double> >
mindriot
  • 5,413
  • 1
  • 25
  • 34
  • This is a very good answer, thanks! Note that if you are using C++17, you can make the std:array-style overload a bit more general by replacing std::size_t with auto. That is: template class Container, class NewType>. This makes it work also with containers where the size parameter is int. QVarLengthArray in Qt is one such example (https://doc.qt.io/qt-5/qvarlengtharray.html) – Keijo Oct 19 '21 at 19:48
3

You might try specializing with template template parameters.

#include <vector>
#include <list>
#include <deque>
#include <string>

template <class T, class NewType>
struct rebind_sequence_container;

template <class ValueT, class Alloc, template <class, class> class Container, class NewType>
struct rebind_sequence_container<Container<ValueT, Alloc>, NewType >
{
     typedef Container<NewType, typename Alloc::template rebind<NewType>::other > type;
};

template <class Container, class NewType>
void test(const NewType& n)
{
    typename rebind_sequence_container<Container, NewType>::type c;
    c.push_back(n);
}

int main()
{
    std::string s;
    test<std::vector<int> >(s);
    test<std::list<int> >(s);
    test<std::deque<int> >(s);
}

However, containers might not have those two template parameters.

Also, in container adapters and associative containers, not just the allocator would need replacing (underlying container in adapters, predicate in std::set). OTOH, their usage is so different from sequence containers that it is hard to imagine a template that works with any container type.

UncleBens
  • 40,819
  • 6
  • 57
  • 90
  • Have you tried it yourself? I tried this, but it didn't work. Please try and confirm. – Nawaz Feb 19 '11 at 19:00
  • I'm surprised. I tried this for almost 1 hour and I didn;t work. But now your solution worked. How come? Do the order of type arguments in the specialized form matter? – Nawaz Feb 19 '11 at 19:08
  • @Nawaz: No idea what was wrong. If it works at all, it would only work for sequence containers. (Rebinding a predicate would get quite insane...) – UncleBens Feb 19 '11 at 19:15
  • sequence containers? What is it? What other containers are there in STL? – Nawaz Feb 19 '11 at 19:25
  • 1
    @Nawaz: Those are vector, list and deque. Queue, priority_queue and stack are container adapters (specific interface on an underlying container). Set, multiset, map and multimap are associative containers. – UncleBens Feb 19 '11 at 21:41
1

You're referring (I believe) to the Policy Clone idiom, using rebind

gnobal
  • 18,309
  • 4
  • 30
  • 35
  • @gnobal: No. that is not what I'm referring to. This idiom assumes this form : `change_value_type`, instead of `change_value_type, B>` – Nawaz Feb 19 '11 at 17:17
  • @Nawaz: You didn't read carefully enough, the idiom assumes the form `vector::rebind`, nowhere is the class template (`vector`) needed. – Ben Voigt Feb 19 '11 at 18:00
  • @Ben: That means, **Thomas** is wrong when he says there is no generic solution? – Nawaz Feb 19 '11 at 18:04
  • 2
    @Nawaz, as far as I understand it, the idiom requires the class being "rebound" to have a specific typedef to implement the "rebounder". Since std::vector and other STL containers don't seem to have anything like this, you can't use this idiom for them. So it's not a generic solution, as it works only for the classes you have control over. – Sergei Tachenov Feb 19 '11 at 18:40
  • @UncleBens, I have tried it, and it complains that there is no `rebind` in the std::vector class. I have looked at the `` header too, but there seems to be nothing like that. Apparently STL uses this idiom only for allocators, not the containers themselves. – Sergei Tachenov Feb 19 '11 at 18:42