消除类似地图和类似矢量的容器之间的模板特化

时间:2014-02-09 14:32:02

标签: c++ templates c++11 stl template-specialization

template<class> struct Printer;

// I want this to match std::vector (and similar linear containers) 
template<template<class, class...> class T, class TV, class... TS> 
    struct Printer<T<TV, TS...>> { ... };

// I want this to match std::map (and similar map-like containers)
template<template<class, class, class...> class TM, class TK, class TV, typename... TS> 
    struct Printer<TM<TK, TV, TS...>> { ... }

int main() 
{
    // Both of these match the second specialization, which is only intended
    // for std::map (and similar map-like containers)
    Printer<std::vector<int>>::something();
    Printer<std::map<int, float>>::something();
}

从示例中可以看出,std::vectorstd::map都匹配第二个专业化。我认为这是因为std::vector的allocator参数与TV匹配,后者用于std::map的值。

如何将std::vector (和其他线性容器)与第一个专业化和std::map (以及其他键值容器)匹配第二个?

3 个答案:

答案 0 :(得分:17)

模式匹配方法的问题在于,只有在为每个容器编写专门化时,它才会起作用。这是一项繁琐的工作。

相反,您可以依赖其他属性:

  • 容器必须通过begin(c)end(c)表达式
  • 进行迭代
  • 除此之外,关联容器将具有::key_type嵌套类型,如§23.2.4[associative.rqmts] 中所述。

因此,我们可以根据标签调度来制作分类器:

inline constexpr auto is_container_impl(...) -> std::false_type {
    return std::false_type{};
}

template <typename C>
constexpr auto is_container_impl(C const* c) ->
    decltype(begin(*c), end(*c), std::true_type{})
{
    return std::true_type{};
}

template <typename C>
constexpr auto is_container(C const& c) -> decltype(is_container_impl(&c)) {
    return is_container_impl(&c);
}

inline constexpr auto is_associative_container_impl(...)
    -> std::false_type
{ return std::false_type{}; }

template <typename C, typename = typename C::key_type>
constexpr auto is_associative_container_impl(C const*) -> std::true_type {
    return std::true_type{};
}

template <typename C>
constexpr auto is_associative_container(C const& c)
    -> decltype(is_associative_container_impl(&c))
{
    return is_associative_container_impl(&c);
}

现在您可以编写“简单”代码:

template <typename C>
void print_container(C const& c, std::false_type/*is_associative*/) {
}

template <typename C>
void print_container(C const& c, std::true_type/*is_associative*/) {
}

template <typename C>
void print_container(C const& c) {
    return print_container(C, is_assocative_container(c));
}

现在,这可能不是您想要的,因为根据此要求,set是一个关联容器,但其值不是pair,因此您无法打印key: value }。您必须根据需要调整标签调度。

答案 1 :(得分:3)

你的问题有点含糊不清,因为也有容器既不是顺序的,也不是“键值”,例如set。我认为你是想区分序列和关联容器吗?

如果是这种情况,您可以依赖关联容器具有key_type的事实,而序列容器则不能。{p>这是一个解决方案:

#include <type_traits>
#include <vector>
#include <map>

template<class, class = void>
struct IsAssociativeContainer
  : std::false_type {};

template<class T>
struct IsAssociativeContainer<T,
    typename std::enable_if<sizeof(typename T::key_type)!=0>::type>
  : std::true_type {};

template<class T, bool = IsAssociativeContainer<T>::value>
struct Printer;

// I want this to match std::vector (and similar linear containers) 
template<template<class, class...> class T, class TV, class... TS> 
    struct Printer<T<TV, TS...>, false> { static void something(); };

// I want this to match std::map (and similar map-like containers)
template<template<class, class, class...> class TM, class TK, class TV, typename... TS> 
    struct Printer<TM<TK, TV, TS...>, true> { static void something(); };

int main() 
{
    // Both of these match the second specialization, which is only intended
    // for std::map (and similar map-like containers)
    Printer<std::vector<int>>::something();
    Printer<std::map<int, float>>::something();
}

Live example

答案 2 :(得分:1)

这里的问题是

template <class, class...> T

template <class, class, class...> TM

两者都匹配任何至少包含2个模板参数的模板类,这两个示例都适用于您的示例。您可以做的一件事是使两个模板参数列表更具体,例如:

template <class>
struct Printer;

template <template<typename, typename> class C, template <typename> class A, typename T>
struct Printer< C<T, A<T>> > {
    ...
};

template <template<typename, typename, typename, typename> class C, template <typename> class Comp, template <typename> class A, typename K, typename T>
struct Printer< C<K, T, Comp<K>, A<std::pair<const K,T>>> > {
    ...
};

你可以在这里看到它适用于std :: vector和std :: map:http://coliru.stacked-crooked.com/a/7f6b8546b1ab5ba9

另一种可能性是使用SFINAE(实际上我建议在两种情况下都使用它):

template<template<class, class...> class T, class TV, class... TS, class = typename std::enable_if<std::is_same<T, std::vector>::value>::type> 
struct Printer<T<TV, TS...>> { ... };

template<template<class, class, class...> class TM, class TK, class TV, typename... TS, class = typename std::enable_if<std::is_same<T, std::map>::value>::type> 
struct Printer<TM<TK, TV, TS...>> { ... }

编辑:Oups,只是在评论中读到你想要匹配的东西'std :: vector'-like,而不是特别是std :: vector。但是第一种方法至少应该区分std :: vector和std :: map。如果你想用不同的迭代方式编写容器的算法,为什么不为迭代器编写函数并区分它们呢?

Edit2:之前的代码非常错误。但它现在有效。