区分地图和集合的模板



在为setunordered_setmapunordered_map创建公共代码时,我需要一些方法,这些方法的处理实际上不同。我的问题是让编译器来推导,使用哪个实现。

举个例子:

#include <map>
#include <unordered_set>
#include <string>
#include <iostream>
using namespace std;
static unordered_set<string>    quiet;
static map<const string, const string>  noisy;
template <template <typename ...> class Set, typename K>
static void insert(Set<K> &store, const string &key, const string &)
{
cout << __PRETTY_FUNCTION__ << "(" << key << ")n";
store.insert(key);
}
template <template <typename ...> class Map, typename K, typename V>
static void insert(Map<K, V> &store, const string &key, const string &v)
{
cout << __PRETTY_FUNCTION__ << "(" << key << ", " << v << ")n";
store.insert(make_pair(key, v));
}
int
main(int, char **)
{
insert(noisy, "cat", "meow");
insert(quiet, "wallaby", ""); /* macropods have no vocal cords */
return 0;
}

虽然cat行有效,但wallaby行会触发编译器的以下错误(clang-10(:

t.cc:22:8: error: no matching member function for call to 'insert'
store.insert(make_pair(key, v));
~~~~~~^~~~~~
t.cc:29:2: note: in instantiation of function template specialization
'insert<unordered_set, std::__1::basic_string<char>, std::__1::hash<std::__1::basic_string<char> > >' requested here
insert(quiet, "wallaby", ""); /* macropods have no vocal cords */

这个错误表明,quietunordered_set,它也被路由到map的插入实现,而不是为unordered_set创建的。

现在,这并非完全没有希望——如果我:

  1. 拼写出所有模板参数,包括可选参数(比较器、分配器等(
    template <template <typename ...> class Set, typename K, typename A, typename C>
    static void insert(Set<K, A, C> &store, const string &key, const string &)
    ...
    template <template <typename ...> class Map, typename K, typename V, typename A, typename C>
    static void insert(Map<K, V, A, C> &store, const string &key, const string &v)
    
  2. unordered_set替换为set

程序将按预期进行编译和工作——编译器将根据每个模板的参数数量(三个与四个(来区分setmap

unordered_setmap具有相同数量的自变量(四个(。。。并且unordered_map五个参数,所以它不会被路由到映射处理方法。。。

如何为要由其处理的两种类型的集合收紧集合处理函数的声明?如何在同一代码中处理mapunordered_map

您可以使用SFINAE技术基本上说:只有当内部的insert调用格式良好时,才考虑此重载。例如:

template <template <typename ...> class Set, typename K>
static auto insert(Set<K> &store, const string &key, const string &)
-> std::void_t<decltype(store.insert(key))>
{
cout << __PRETTY_FUNCTION__ << "(" << key << ")" << endl;
store.insert(key);
}
template <template <typename ...> class Map, typename K, typename V>
static auto insert(Map<K, V> &store, const string &key, const string &v)
-> std::void_t<decltype(store.insert(make_pair(key, v)))>
{
cout << __PRETTY_FUNCTION__ << "(" << key << ", " << v << ")" << endl;
store.insert(make_pair(key, v));
}

演示

std::mapstd::unordered_map都有mapped_type成员类型,而它们的对应set没有。因此,我们可以在std::void_t:的帮助下添加一些SFINAE

template<template<typename...> class Map, typename K, typename V,
typename = std::void_t<typename Map<K, V>::mapped_type>>
void insert(Map<K, V>&, const string&, const string&) {
// ...
}

如果你需要(在你的例子中你不需要(约束两个函数模板,一个更通用的解决方案是:

template<class, typename = void>
struct is_map : std::false_type { };
template<class Map>
struct is_map<Map, std::void_t<typename Map::mapped_type>> : std::true_type { };
template<template<typename...> class Set, typename K, 
std::enable_if_t<!is_map<Set<K>>::value, int> = 0>
void insert(Set<K>&, const string&, const string&) {
// ...
}
template<template <typename...> class Map, typename K, typename V,
std::enable_if_t<is_map<Map<K, V>>::value, int> = 0>
void insert(Map<K, V>&, const string&, const string&) {
// ...
}

C++11解决方案:

template<class...>  // or just <class> if genericity is not needed
struct std_void {
using type = void;
};
template<template<typename...> class Map, typename K, typename V,
typename = typename std_void<typename Map<K, V>::mapped_type>::type>
void insert(Map<K, V>&, const string&, const string&) {
// ...
}

添加了注释问题中的代码针对GCC 4.4.7。这是一个相当旧的GCC版本,它不完全支持C++11标准。特别是,它不支持using类型的别名,所以std_void应该通过老式的typedef:来实现

template<class...>
struct std_void {
typedef void type;
};

首先,实际的解决方案是:

template<class...>  // or just <class> if genericity is not needed
struct std_void {
typedef void type;
};
template<template<typename...> class Map, typename K, typename V,
typename = std_void<typename Map<K, V>::mapped_type>>
void insert(Map<K, V>& store, const string &key, const string &value) {
// ...
}

对于来自(辉煌的(未来的读者,请使用@Evg提出的技术(这项技术是基于他的建议(或@Igor Tandetnik-提出的技术,如果您的编译器支持必要的功能。

Evg的答案——以及由此得出的答案——根据模板的类型是否定义了mapped_type来选择模板。Igor对模板的insert-方法所采用的参数进行了区分。两者都适合我的目的,但Evg的更容易适应旧的编译器。


最后,我必须发泄我对C++状态的不满:这不应该那么困难。我让它工作了,但一位试图阅读我的代码的同事不会理解它——如果没有多次通过和无数的诅咒和";明白了">

是的,C++程序被编译(成二进制代码,而不是"字节"代码(,并且运行时类型信息(RTTI(是完整的"字节"所必需的;反射;(非常(贵。

但我所需要的并不需要运行时反思!所有必要的信息在编译时都是已知的,那么为什么在它首次引入三十年后,我还没有所有必要的语言功能呢?即使是10年的等待也太长了——芯片制造商在同一时期使处理器胖了32倍。

constexpr就是这样,谢谢你,但它是最近添加的,我的编译器仍然不支持它…

您必须使用部分模板专用化。像这样:

template<template<typename... > class Map, typename T, typename U> static void
insert (Map<T,U>&, const T&, const U&); // generic function
template<typename T, typename U> static void
insert<std::map<T,U>, T, U> (std::map<T,U>&, const T&, const U&); // this is only called if Map is std::map
// you can put more specializations here

第一个函数是一个泛型函数,如果类型Map没有出现在同一函数的任何模板专用化中,则将为除之外的所有Map调用该函数。第二个函数与第一个函数相同,但只有当Map类型为std::Map<T、 U>。

请参阅维基百科关于部分模板专业化的文章

最新更新