使用模板C++任何集合类型的包装器
C++ wrapper around any collection type using templates
对c++ 来说非常陌生,但是有一个关于模板的问题
假设我有一个简单的模板类,定义如下:
template<typename Collection>
class MySack {
private:
Collection c;
public:
typedef typename Collection::value_type value_type;
void add(const value_type& value) {
c.push_back(value);
}
};
该类的目的是接受任何类型的集合,并允许用户为指定的typename Collection
插入正确类型的值。
明显的问题是,这仅适用于定义了push_back
方法的类型,这意味着它可以与list
一起使用,但不适用于set
。
我开始阅读有关模板专业化的信息,看看这是否有任何帮助,但是我认为这不会提供解决方案,因为必须知道集合中包含的类型。
如何在 c++ 中解决这个问题?
您可以使用std::experimental::is_detected
和if constexpr
来使其工作:
template<class C, class V>
using has_push_back_impl = decltype(std::declval<C>().push_back(std::declval<V>()));
template<class C, class V>
constexpr bool has_push_back = std::experimental::is_detected_v<has_push_back_impl, C, V>;
template<typename Collection>
class MySack {
private:
Collection c;
public:
typedef typename Collection::value_type value_type;
void add(const value_type& value) {
if constexpr (has_push_back<Collection, value_type>) {
std::cout << "push_back.n";
c.push_back(value);
} else {
std::cout << "insert.n";
c.insert(value);
}
}
};
int main() {
MySack<std::set<int>> f;
f.add(23);
MySack<std::vector<int>> g;
g.add(23);
}
你可以切换到insert
成员函数,它对std::vector
、std::set
、std::list
和其他容器具有相同的语法:
void add(const value_type& value) {
c.insert(c.end(), value);
}
在 C++11 中,您可能还希望为 rvalue 参数创建一个版本:
void add(value_type&& value) {
c.insert(c.end(), std::move(value));
}
而且,一种模拟放置语义(实际上不是真的):
template <typename... Ts>
void emplace(Ts&&... vs) {
c.insert(c.end(), value_type(std::forward<Ts>(vs)...));
}
...
int main() {
using value_type = std::pair<int, std::string>;
MySack<std::vector<value_type>> v;
v.emplace(1, "first");
MySack<std::set<value_type>> s;
s.emplace(2, "second");
MySack<std::list<value_type>> l;
l.emplace(3, "third");
}
我开始阅读有关模板专业化的信息,看看是否会 任何帮助,但是我认为这不会提供解决方案,因为 必须知道集合中包含的类型。
您可以部分专化MySack
以使用std::set
.
template <class T>
class MySack<std::set<T>> {
//...
};
但是,这样做的缺点是部分专用化取代了整个类定义,因此需要再次定义所有成员变量和函数。
更灵活的方法是使用基于策略的设计。在这里,您将添加一个模板参数,用于包装特定于容器的操作。您可以为最常见的情况提供默认值,但用户可以为其他情况提供自己的策略。
template <class C, class V = typename C::value_type>
struct ContainerPolicy
{
static void push(C& container, const V& value) {
c.push_back(value);
}
static void pop(C& container) {
c.pop_back();
}
};
template <class C, class P = ContainerPolicy<C>>
class MySack
{
Collection c;
public:
typedef typename Collection::value_type value_type;
void add(const value_type& value) {
P::push(c, value);
}
};
在这种情况下,为默认策略提供部分模板专用化会更容易,因为它仅包含与使用的特定容器相关的功能。其他逻辑仍然可以在MySack
类模板中捕获,而无需复制代码。
现在,您也可以将MySack
与您自己的或不遵循 STL 样式的第三方容器一起使用。您只需提供自己的策略。
struct MyContainer {
void Add(int value);
//...
};
struct MyPolicy {
static void push(MyContainer& c, int value) {
c.Add(value);
}
};
MySack<MyContainer, MyPolicy> sack;
如果你至少可以使用C++11,我建议创建一个模板递归结构
template <std::size_t N>
struct tag : public tag<N-1U>
{ };
template <>
struct tag<0U>
{ };
在容器可以支持多个添加函数的情况下管理优先级。
因此,您可以在类的private
部分添加以下模板帮助程序函数
template <typename D, typename T>
auto addHelper (T && t, tag<2> const &)
-> decltype((void)std::declval<D>().push_back(std::forward<T>(t)))
{ c.push_back(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<1> const &)
-> decltype((void)std::declval<D>().insert(std::forward<T>(t)))
{ c.insert(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<0> const &)
-> decltype((void)std::declval<D>().push_front(std::forward<T>(t)))
{ c.push_front(std::forward<T>(t)); }
请注意,只有当启用了相应的方法(push_back()
、insert()
或push_front()
)时,decltype()
部分才会启用它们(通过SFINAE)。
现在你可以写add()
,在public
部分,如下所示
template <typename T>
void add (T && t)
{ addHelper<C>(std::forward<T>(t), tag<2>{}); }
tag<2>
元素使 因此调用tag<2>
addHelper()
方法(如果可用(如果push_back()
可用于类型C
),否则称为tag<1>
方法(insert()
方法)(如果可用),否则称为tag<0>
方法(push_front()
方法)可用。否则错误。
还要观察T && t
和std::forward<T>(t)
部分。这样,您应该选择正确的语义:复制或移动。
以下是完整的工作示例
#include <map>
#include <set>
#include <list>
#include <deque>
#include <vector>
#include <iostream>
#include <forward_list>
#include <unordered_map>
#include <unordered_set>
template <std::size_t N>
struct tag : public tag<N-1U>
{ };
template <>
struct tag<0U>
{ };
template <typename C>
class MySack
{
private:
C c;
template <typename D, typename T>
auto addHelper (T && t, tag<2> const &)
-> decltype((void)std::declval<D>().push_back(std::forward<T>(t)))
{ c.push_back(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<1> const &)
-> decltype((void)std::declval<D>().insert(std::forward<T>(t)))
{ c.insert(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<0> const &)
-> decltype((void)std::declval<D>().push_front(std::forward<T>(t)))
{ c.push_front(std::forward<T>(t)); }
public:
template <typename T>
void add (T && t)
{ addHelper<C>(std::forward<T>(t), tag<2>{}); }
};
int main ()
{
MySack<std::vector<int>> ms0;
MySack<std::deque<int>> ms1;
MySack<std::set<int>> ms2;
MySack<std::multiset<int>> ms3;
MySack<std::unordered_set<int>> ms4;
MySack<std::unordered_multiset<int>> ms5;
MySack<std::list<int>> ms6;
MySack<std::forward_list<int>> ms7;
MySack<std::map<int, long>> ms8;
MySack<std::multimap<int, long>> ms9;
MySack<std::unordered_map<int, long>> msA;
MySack<std::unordered_multimap<int, long>> msB;
ms0.add(0);
ms1.add(0);
ms2.add(0);
ms3.add(0);
ms4.add(0);
ms5.add(0);
ms6.add(0);
ms7.add(0);
ms8.add(std::make_pair(0, 0L));
ms9.add(std::make_pair(0, 0L));
msA.add(std::make_pair(0, 0L));
msB.add(std::make_pair(0, 0L));
}
- 调用具有未标识类型的类的方法
- C++ 中模板化类型的类层次结构
- 根据模板类型选择类模板的成员类型?
- C++从抽象类型定义类成员
- 实例化具有不完整类型的类模板格式不正确(如果该类型是在之后定义的)
- 重载模板<类型名...>类的函数模板
- 在设计方面:重载vector类型的类成员的插入运算符
- 错误:使用 SWIG 的未知类型名称"类"
- 添加字符串类型的类成员会导致调用基类函数而不是子函数
- 包装任意类型/非类型模板类的模板类
- 整数到模板类型集合(C++)
- 使用相同方法但不同成员类型构建类的最佳方法
- 如何删除类内类类型的类成员指针
- 如何读写Qt集合类
- 为什么我不能在同一行中定义两个相同类型的类的成员指针
- 如何仅从类类型推断类构造函数参数的类型
- 大三实现在一个集合类
- (C++);动态决定函数的类型(派生类)
- 具有容器变量类型的类模板
- 如何基于模板参数类型施放类模板