区分地图和集合的模板
Template distinguishing between maps and sets
在为 set
、unordered_set
、map
和 unordered_map
创建代码 common 时,我需要一些方法,其中处理实际上是不同的。我的问题是让编译器推断出要使用哪个实现。
考虑示例:
#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 */
错误很明显,quiet
,它是一个 unordered_set
,也被路由到 map
的插入实现——而不是为 unordered_set
.
现在,这并非完全没有希望——如果我:
- 拼出 所有 模板参数——包括可选参数(比较器、分配器等)
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)
- 将
unordered_set
替换为 set
。
程序将按预期编译和工作 -- 编译器将根据每个模板采用的参数数量(三个对四个)区分 set
和 map
。
但是 unordered_set
的参数数量与 map
相同(四个)...而 unordered_map
有 5 个 个参数,所以它不会被路由到地图处理方法...
我如何加强集合处理函数的声明以使其能够处理两种类型的集合?
如何在同一代码中同时处理 map
和 unordered_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::map
和 std::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<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.
时才会执行函数体
首先,实际的解决方案,适用于编译器版本 当前 在野外找到的 -- 使用 gcc-4.4.7(RedHat6 上的标准编译器)测试, gcc-8.3.1 和 clang-10 是:
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) {
// ...
}
为了(光荣的)未来的 reader,一定要使用@Evg 提出的技术(这个提议基于他的提议)或@Igor-Tandetnik 提出的技术 -- 如果你的编译器支持必要的特性。
Evg 的答案——以及从中得出的这个答案——select 模板基于其类型是否定义了 mapped_type
。 Igor 对模板的 insert
方法采用的参数进行了区分。两者都适合我的目的,但 Evg 的更容易适应旧的编译器。
最后,我必须发泄一下我对 C++ 状态的不满:这不应该那么困难。我让它工作了,但一位试图阅读我的代码的同事不会理解它——不是没有多次通过它和无数的诅咒和“陷阱!”
是的,C++ 程序被编译(编译成二进制代码,而不是“字节”代码),完整“反射”所需的 运行 时间类型信息 (RTTI) 是(非常)贵。
但我需要的不需要运行时间反思!所有必要的信息在 编译时 都是已知的,那么为什么我不已经拥有所有必要的语言特性,在它首次被引入三十年后?即使是 10 年等待这样的功能也太长了——芯片制造商在同一时期内使处理器性能提高 32 倍。
constexpr
有点像,谢谢您,但是最近才添加,我的编译器仍然不支持它...
在为 set
、unordered_set
、map
和 unordered_map
创建代码 common 时,我需要一些方法,其中处理实际上是不同的。我的问题是让编译器推断出要使用哪个实现。
考虑示例:
#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 */
错误很明显,quiet
,它是一个 unordered_set
,也被路由到 map
的插入实现——而不是为 unordered_set
.
现在,这并非完全没有希望——如果我:
- 拼出 所有 模板参数——包括可选参数(比较器、分配器等)
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)
- 将
unordered_set
替换为set
。
程序将按预期编译和工作 -- 编译器将根据每个模板采用的参数数量(三个对四个)区分 set
和 map
。
但是 unordered_set
的参数数量与 map
相同(四个)...而 unordered_map
有 5 个 个参数,所以它不会被路由到地图处理方法...
我如何加强集合处理函数的声明以使其能够处理两种类型的集合?
如何在同一代码中同时处理 map
和 unordered_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::map
和 std::unordered_map
都有 mapped_type
成员类型,而它们的 set
成员类型没有。因此,我们可以在 std::void_t
:
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<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
首先,实际的解决方案,适用于编译器版本 当前 在野外找到的 -- 使用 gcc-4.4.7(RedHat6 上的标准编译器)测试, gcc-8.3.1 和 clang-10 是:
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) {
// ...
}
为了(光荣的)未来的 reader,一定要使用@Evg 提出的技术(这个提议基于他的提议)或@Igor-Tandetnik 提出的技术 -- 如果你的编译器支持必要的特性。
Evg 的答案——以及从中得出的这个答案——select 模板基于其类型是否定义了 mapped_type
。 Igor 对模板的 insert
方法采用的参数进行了区分。两者都适合我的目的,但 Evg 的更容易适应旧的编译器。
最后,我必须发泄一下我对 C++ 状态的不满:这不应该那么困难。我让它工作了,但一位试图阅读我的代码的同事不会理解它——不是没有多次通过它和无数的诅咒和“陷阱!”
是的,C++ 程序被编译(编译成二进制代码,而不是“字节”代码),完整“反射”所需的 运行 时间类型信息 (RTTI) 是(非常)贵。
但我需要的不需要运行时间反思!所有必要的信息在 编译时 都是已知的,那么为什么我不已经拥有所有必要的语言特性,在它首次被引入三十年后?即使是 10 年等待这样的功能也太长了——芯片制造商在同一时期内使处理器性能提高 32 倍。
constexpr
有点像,谢谢您,但是最近才添加,我的编译器仍然不支持它...