如何正确声明和定义泛型模板函数 flip_map?
结论
将默认比较器改为 std::less<B>:
template <typename A, typename B, typename Cmp = std::less<B>>
std::multimap<B, A, Cmp> flip_map(const std::map<A, B>&);
交换键和值后,目标 std::multimap 的键类型为 B,因此比较器应比较两个 B 类型的值。如果函数要供多个源文件使用,通常应把模板定义也放进头文件,让实例化时能够看到实现。
为什么 iter_value_t 会报错?
std::iter_value_t<I> 用来获取迭代器或满足相应读取要求的类型 I 所关联的值类型。这里的 B 是源 map 的映射值类型,不是迭代器。
对于调用:
std::map<std::string, std::size_t> myKeyCount;
auto myFlippedMap = flip_map(myKeyCount);
编译器推导得到:
A = std::string
B = std::size_t
原声明随后尝试形成 std::iter_value_t<std::size_t>。整数类型没有所需的迭代器关联值类型,因此默认模板实参替换失败。报错中的 long unsigned int 是该编译环境下 std::size_t 的底层类型。
另外,Cmp 只出现在返回类型中,无法从参数 src 推导出来。即使赋值目标写成 std::multimap<std::size_t, std::string>,也无法帮助这个函数调用推导 Cmp。因此需要给 Cmp 设置有效的默认值,或者在调用时显式指定它。
保留现有结构的完整实现
下面的代码可以放在头文件中。它使用 std::ranges::transform,需要 C++20 或更高标准,以及相应的标准库支持。
#pragma once
#include <algorithm>
#include <functional>
#include <iterator>
#include <map>
#include <utility>
template <typename A, typename B>
std::pair<B, A> flip_pair(const std::pair<A, B>& p);
template <typename A, typename B, typename Cmp = std::less<B>>
std::multimap<B, A, Cmp> flip_map(const std::map<A, B>& src);
template <typename A, typename B>
std::pair<B, A> flip_pair(const std::pair<A, B>& p)
{
return std::pair<B, A>(p.second, p.first);
}
template <typename A, typename B, typename Cmp>
std::multimap<B, A, Cmp> flip_map(const std::map<A, B>& src)
{
std::multimap<B, A, Cmp> dst;
std::ranges::transform(
src,
std::inserter(dst, dst.begin()),
flip_pair<A, B>
);
return dst;
}
默认模板实参只在前面的声明中指定,后面的定义不再重复指定。
调用示例:
#include <cstddef>
#include <string>
std::map<std::string, std::size_t> myKeyCount{
{"apple", 2},
{"banana", 2},
{"pear", 1}
};
std::multimap<std::size_t, std::string> myFlippedMap =
flip_map(myKeyCount);
myFlippedMap 会保留两个键为 2 的元素,并按照新键的比较规则排序。
更直接的循环写法
如果不需要单独使用 flip_pair,可以用循环直接构造目标元素:
#include <functional>
#include <map>
template <typename A, typename B, typename Cmp = std::less<B>>
std::multimap<B, A, Cmp> flip_map(const std::map<A, B>& src)
{
std::multimap<B, A, Cmp> dst;
for (const auto& [key, value] : src) {
dst.emplace(value, key);
}
return dst;
}
这个版本使用结构化绑定,需要 C++17 或更高标准,可以替换前面的实现。
原来的辅助函数也能用于上述 transform。虽然 map 的元素类型是 std::pair<const A, B>,但只要键和值可复制,它就可以转换成临时的 std::pair<A, B>,再绑定到 flip_pair<A, B> 的常量引用参数。循环版本避免了这个临时 pair。
模板定义位置与使用限制
如果模板定义和调用都在同一个 .cpp 中,且实例化时定义可见,就可以正常使用。若其他 .cpp 只能看到头文件里的声明,通常还需要让它们看到模板定义,否则可能出现链接错误。常见做法是把定义放在头文件中,或者放入由头文件包含的 .tpp 文件。只有在支持的类型组合固定时,才适合在 .cpp 中通过显式实例化提供对应实现。
还需要注意:
- 这个实现从
const源容器复制键和值,适用于相应类型可复制的情况。 std::less<B>要求B支持满足严格弱序要求的比较;不满足时需要提供合适的比较器。- 当前函数参数是
std::map<A, B>,只匹配使用默认源比较器和默认分配器的 map。如果还要接受自定义源比较器或分配器,需要为它们增加模板参数。 - 当前实现默认构造
Cmp。如果比较器带有必须由调用者提供的状态,还需要增加比较器对象参数,并用它构造目标 multimap。
备注:内容仅供参考。
