gpt4 book ai didi

c++ - 返回转换容器的 std::transform-like 函数

转载 作者:IT老高 更新时间:2023-10-28 12:30:01 24 4
gpt4 key购买 nike

我正在尝试实现类似于 std::transform 的功能算法,但不是通过参数获取输出迭代器,我想创建并返回一个带有转换输入元素的容器。

假设它名为 transform_container并接受两个参数:容器和仿函数。它应该返回相同的容器类型,但可能由不同的元素类型参数化(仿函数可以返回不同类型的元素)。

我想使用我的函数,如下例所示:

std::vector<int> vi{ 1, 2, 3, 4, 5 };
auto vs = transform_container(vi, [] (int i) { return std::to_string(i); });
//vs will be std::vector<std::string>
assert(vs == std::vector<std::string>({"1", "2", "3", "4", "5"}));

std::set<int> si{ 5, 10, 15 };
auto sd = transform_container(si, [] (int i) { return i / 2.; });
//sd will be of type std::set<double>
assert(sd == std::set<double>({5/2., 10/2., 15/2.}));

我可以写两个函数——一个用于 std::set一个用于 std::vector - 这似乎工作正常。除了容器类型名称之外,它们是相同的。他们的代码如下。
template<typename T, typename Functor>
auto transform_container(const std::vector<T> &v, Functor &&f) -> std::vector<decltype(f(*v.begin()))>
{
std::vector<decltype(f(*v.begin()))> ret;
std::transform(std::begin(v), std::end(v), std::inserter(ret, ret.end()), f);
return ret;
}

template<typename T, typename Functor>
auto transform_container(const std::set<T> &v, Functor &&f) -> std::set<decltype(f(*v.begin()))>
{
std::set<decltype(f(*v.begin()))> ret;
std::transform(std::begin(v), std::end(v), std::inserter(ret, ret.end()), f);
return ret;
}

但是,当我尝试将它们合并为一个适用于任何容器的通用函数时,我遇到了许多问题。 setvector是类模板,所以我的函数模板必须采用模板模板参数。此外,集合和 vector 模板具有不同数量的类型参数,需要适当调整。

将上述两个函数模板泛化为适用于任何兼容容器类型的函数的最佳方法是什么?

最佳答案

最简单的情况:匹配容器类型
对于输入类型与输出类型匹配的简单情况(我已经意识到这不是您要问的),请提高一级。而不是指定类型 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 ,并让它转换 TU , 和 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::mapstd::array给党带来更多问题。
搬运 std::mapstd::unordered_map还不错。主要问题是 replace_type需要更换更多类型。不仅有 T -> U更换,也是 std::pair<T, T2> -> std::pair<U, U2>替代品。这增加了对不需要的类型替换的关注程度,因为飞行中的类型不止一种。也就是说,这就是我发现的工作;请注意,在测试中,我需要指定转换我的 map 对的 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 的刺激和鼓励。

关于c++ - 返回转换容器的 std::transform-like 函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23871757/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com