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

时间:2018-04-20 06:22:43

标签: c++ templates collections

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 ++中解决这个问题?

4 个答案:

答案 0 :(得分:3)

您可以使用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);
}

答案 1 :(得分:3)

您可以切换到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));
}

并且,类似于模拟emplace语义(实际上并非真实):

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

答案 2 :(得分:1)

如果你至少可以使用C ++ 11,我建议创建一个模板递归struct

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

观察decltype()部分仅在相应方法(push_back()insert()push_front())启用时启用它们(通过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));
 }

答案 3 :(得分:1)

  

我开始阅读有关模板专业化的内容,看看是否存在   任何帮助,但我不认为这将提供一个解决方案   必须知道集合中包含的类型。

您可以部分专门化MySack以使用std::set

template <class T>
class MySack<std::set<T>> {
    //...
};

但是,这样做的缺点是部分特化取代了整个类定义,所以你需要再次定义所有成员变量和函数。

更灵活的方法是使用policy-based design。在这里,您添加一个包装特定于容器的操作的模板参数。您可以为最常见的情况提供默认值,但用户可以为其他情况提供自己的策略。

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;