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 ++中解决这个问题?
答案 0 :(得分:3)
您可以使用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);
}
答案 1 :(得分:3)
您可以切换到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));
}
并且,类似于模拟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 && 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));
}
答案 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;