使用模板C++任何集合类型的包装器

C++ wrapper around any collection type using templates

本文关键字:类型 集合类 包装 集合 任何 C++      更新时间:2023-10-16

对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_detectedif 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::vectorstd::setstd::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 && tstd::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));
}