最简单的情况:匹配容器类型
对于输入类型与输出类型匹配的简单情况(我已经意识到这不是您要问的),上一级。无需指定容器使用的类型T,并尝试专门处理vector<T> 等,只需指定容器本身的类型即可:
template <typename Container, typename Functor>
Container transform_container(const Container& c, Functor &&f)
{
Container ret;
std::transform(std::begin(c), std::end(c), std::inserter(ret, std::end(ret)), f);
return ret;
}
更复杂:兼容的值类型
由于您想尝试更改容器存储的项目类型,您需要使用模板模板参数,并将T 修改为返回容器使用的。
template <
template <typename T, typename... Ts> class Container,
typename Functor,
typename T, // <-- This is the one we'll override in the return container
typename U = std::result_of<Functor(T)>::type,
typename... Ts
>
Container<U, Ts...> transform_container(const Container<T, Ts...>& c, Functor &&f)
{
Container<U, Ts...> ret;
std::transform(std::begin(c), std::end(c), std::inserter(ret, std::end(ret)), f);
return ret;
}
什么是不兼容的值类型?
这只会让我们走到一半。它适用于从signed 到unsigned 的转换,但是当使用T=int 和U=std::string 解析并处理集合时,它会尝试实例化std::set<std::string, std::less<int>, ...>,因此不会编译。
为了解决这个问题,我们希望采用任意一组参数并将T 的实例替换为U,即使它们是其他模板参数的参数。因此std::set<int, std::less<int>> 应该变成std::set<std::string, std::less<std::string>>,以此类推。正如其他答案所建议的那样,这涉及一些自定义模板元编程。
模板元编程来拯救
让我们创建一个模板,将其命名为 replace_type,然后将其将 T 转换为 U,并将 K<T> 转换为 K<U>。首先让我们处理一般情况。如果不是模板类型,并且不匹配T,则其类型应保持K:
template <typename K, typename ...>
struct replace_type { using type = K; };
然后是专业。如果不是模板类型,并且匹配T,则其类型应为U:
template <typename T, typename U>
struct replace_type<T, T, U> { using type = U; };
最后是处理模板类型参数的递归步骤。对于模板化类型参数中的每种类型,相应地替换类型:
template <template <typename... Ks> class K, typename T, typename U, typename... Ks>
struct replace_type<K<Ks...>, T, U>
{
using type = K<typename replace_type<Ks, T, U>::type ...>;
};
最后更新transform_container 以使用replace_type:
template <
template <typename T, typename... Ts> class Container,
typename Functor,
typename T,
typename U = typename std::result_of<Functor(T)>::type,
typename... Ts,
typename Result = typename replace_type<Container<T, Ts...>, T, U>::type
>
Result transform_container(const Container<T, Ts...>& c, Functor &&f)
{
Result ret;
std::transform(std::begin(c), std::end(c), std::inserter(ret, std::end(ret)), f);
return ret;
}
完成了吗?
这种方法的问题在于它不一定安全。如果您从Container<MyCustomType> 转换为Container<SomethingElse>,那可能没问题。但是当从Container<builtin_type> 转换为Container<SomethingElse> 时,另一个模板参数不应该从builtin_type 转换为SomethingElse 是合理的。此外,像std::map 或std::array 这样的替代容器会给聚会带来更多问题。
处理std::map 和std::unordered_map 还不错。主要问题是replace_type 需要替换更多类型。不仅有T -> U 替换,还有std::pair<T, T2> -> std::pair<U, U2> 替换。这增加了对不需要的类型替换的关注程度,因为飞行中的类型不止一种。也就是说,这就是我发现的工作;请注意,在测试中,我需要指定转换地图对的 lambda 函数的返回类型:
// map-like classes are harder. You have to replace both the key and the key-value pair types
// Give a base case replacing a pair type to resolve ambiguities introduced below
template <typename T1, typename T2, typename U1, typename U2>
struct replace_type<std::pair<T1, T2>, std::pair<T1, T2>, std::pair<U1, U2>>
{
using type = std::pair<U1, U2>;
};
// Now the extended case that replaces T1->U1 and pair<T1,T2> -> pair<T2,U2>
template <template <typename...> class K, typename T1, typename T2, typename U1, typename U2, typename... Ks>
struct replace_type<K<T1, T2, Ks...>, std::pair<const T1, T2>, std::pair<const U1, U2>>
{
using type = K<U1, U2,
typename replace_type<
typename replace_type<Ks, T1, U1>::type,
std::pair<const T1, T2>,
std::pair<const U1, U2>
>::type ...
>;
};
std::array 呢?
处理std::array 增加了痛苦,因为它的模板参数不能在上面的模板中推导出来。正如 Jarod42 所指出的,这是由于它的参数包括值而不仅仅是类型。我已经通过添加专业化和引入帮助器 contained_type 来为我提取 T (旁注,每个构造函数,这最好写成更简单的 typename Container::value_type 并适用于我在这里讨论过的所有类型) .即使没有 std::array 专业化,这也允许我将 transform_container 模板简化为以下内容(即使不支持 std::array,这也可能是一个胜利):
template <typename T, size_t N, typename U>
struct replace_type<std::array<T, N>, T, U> { using type = std::array<U, N>; };
// contained_type<C>::type is T when C is vector<T, ...>, set<T, ...>, or std::array<T, N>.
// This is better written as typename C::value_type, but may be necessary for bad containers
template <typename T, typename...>
struct contained_type { };
template <template <typename ... Cs> class C, typename T, typename... Ts>
struct contained_type<C<T, Ts...>> { using type = T; };
template <typename T, size_t N>
struct contained_type<std::array<T, N>> { using type = T; };
template <
typename Container,
typename Functor,
typename T = typename contained_type<Container>::type,
typename U = typename std::result_of<Functor(T)>::type,
typename Result = typename replace_type<Container, T, U>::type
>
Result transform_container(const Container& c, Functor &&f)
{
// as above
}
但是,transform_container 的当前实现使用 std::inserter,它不适用于 std::array。虽然可以进行更多专业化,但我将把它作为模板汤练习留给感兴趣的读者。在大多数情况下,我个人会选择不支持std::array。
View the cumulative live example
完全披露:虽然这种方法受到 Ali 引用 Kerrek SB 答案的影响,但我没有设法让它在 Visual Studio 2013 中工作,所以我自己构建了上述替代方案。非常感谢Kerrek SB's original answer的部分内容仍然需要,以及来自Constructor和Jarod42的推动和鼓励。