【问题标题】:Changing value_type of a given STL container更改给定 STL 容器的 value_type
【发布时间】:2011-02-19 17:07:31
【问题描述】:

假设,我有一个 STL 容器类型(不是对象),比如vector<A>。现在是value_typeA,所以我想把它改成B

基本上,我想要一个这种形式的类模板,或者它的变体:

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

以便我可以通过以下方式使用它:

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

意思是,new_containervector&lt;B&gt;

有可能吗?

【问题讨论】:

  • 如果是set_value_type&lt;vector, B&gt;的形式呢?
  • @KennyTM:不,那不能做我想做的事。我可以使用该表格。当我将vectortype 一起使用时,问题就出现了,比如vector&lt;A&gt;

标签: c++ templates stl metaprogramming containers


【解决方案1】:

当我试图从根本上解决同样的问题时,我偶然发现了这一点。它甚至可以在不依赖于rebind 特定于std::allocator 的类型的情况下工作——唯一的要求是反弹值类型是各个类的第一个模板参数。这适用于所有相关的 STL 类(std::vectorstd::setstd::list 等,以及例如 std::lessstd::allocator)。

C++11 之前的解决方案如下所示:

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 让它更容易一些:

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;
};

为了支持std::array,可以添加以下内容:

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;
};

结果几乎可以用于任何 STL 类型:

#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;
}

在 Linux 系统上运行此程序并将输出通过c++filt -t 传递给您

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> >

【讨论】:

  • 这是一个很好的答案,谢谢!请注意,如果您使用的是 C++17,则可以通过将 std::size_t 替换为 auto 来使 std:array 样式的重载更加通用。即:template class Container, class NewType>。这使得它也适用于 size 参数为 int 的容器。 Qt 中的 QVarLengthArray 就是这样一个例子 (doc.qt.io/qt-5/qvarlengtharray.html)
【解决方案2】:

您可以尝试专门使用模板模板参数。

#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);
}

但是,容器可能没有这两个模板参数。

此外,在容器适配器和关联容器中,不仅需要替换分配器(适配器中的基础容器,std::set 中的谓词)。 OTOH,它们的用法与序列容器如此不同,以至于很难想象一个模板适用于任何容器类型。

【讨论】:

  • 你自己试过了吗?我试过这个,但它没有用。请尝试确认。
  • 我很惊讶。我尝试了将近 1 小时,但我没有工作。但现在你的解决方案奏效了。怎么会?特殊形式中类型参数的顺序重要吗?
  • @Nawaz:不知道出了什么问题。如果它完全有效,它只适用于序列容器。 (重新绑定谓词会变得非常疯狂......)
  • 序列容器?它是什么? STL 中还有哪些其他容器?
  • @Nawaz:它们是向量、列表和双端队列。 Queue、priority_queue 和 stack 是容器适配器(底层容器上的特定接口)。 Set、multiset、map 和 multimap 是关联容器。
【解决方案3】:

您指的是(我相信)the Policy Clone idiom,使用重新绑定

【讨论】:

  • @gnobal:不,我指的不是这个。这个成语采用这种形式:change_value_type&lt;vector, B&gt;,而不是change_value_type&lt;vector&lt;A&gt;, B&gt;
  • @Nawaz:你看的不够仔细,成语采用vector&lt;A&gt;::rebind&lt;B&gt;的形式,哪里都不需要类模板(vector)。
  • @Ben:也就是说,Thomas 说没有通用解决方案是错误的?
  • @Nawaz,据我了解,成语要求被“反弹”的类具有特定的 typedef 来实现“反弹器”。由于 std::vector 和其他 STL 容器似乎没有这样的东西,所以你不能对它们使用这个成语。所以它不是一个通用的解决方案,因为它只适用于你可以控制的类。
  • @UncleBens,我试过了,它抱怨 std::vector 类中没有rebind。我也看过&lt;vector&gt; 标头,但似乎没有这样的东西。显然 STL 仅将这个习惯用法用于分配器,而不是容器本身。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多