PAYATHON 2026

如何正确声明和定义泛型模板函数 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。

备注:内容仅供参考。