消除类似映射和类似矢量的容器之间的模板专用化

Disambiguate template specialization between map-like and vector-like containers

本文关键字:之间 专用 映射      更新时间:2023-10-16
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的分配器参数与TV匹配,这是为了std::map的值。

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

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

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

  • 容器必然可以通过begin(c)end(c)表达式进行迭代
  • 除此之外,关联容器将具有::key_type嵌套类型,如§ 23.2.4 [associaciative.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。您必须根据自己的需要调整标签调度。

你的问题有点模棱两可,因为还有一些容器既不是顺序的,也不是"键值"的,例如 set .我认为你的意思是区分序列和关联容器?

如果是这种情况,你可以依赖关联容器有key_type,而序列容器没有。这是一个解决方案:

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

现场示例

这里的问题是

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...>> { ... }

编辑:哎呀,只需在评论中阅读您想要匹配"std::vector"之类的内容,而不是特定的标准::vector。然而,第一种方法至少应该区分std::vector和std::map。如果你想用不同的迭代方式为容器编写算法,为什么不为迭代器编写函数并区分它们呢?

编辑2:以前的代码大错特错。但是它现在可以工作了。