类似std :: transform的函数返回转换的容器

我想实现一个类似于std::transformalgorithm的函数,而不是通过我想创build的参数取得输出迭代器,并返回一个带有已转换input元素的容器。

假设它被命名为transform_container并且有两个参数:容器和函子。 它应该返回相同的容器types,但可能通过不同的元素types进行参数化(Functor可以返回不同types的元素)。

我想使用我的function,如下面的例子:

 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 – 这似乎可以正常工作。 它们是相同的,除了容器types名称。 他们的代码如下所示。

 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是类模板,所以我的函数模板必须采用模板模板参数。 而且,set和vector模板有不同数量的types参数需要适当的调整。

将上述两个函数模板推广到可与任何兼容容器types一起使用的函数的最佳方法是什么?

最简单的情况:匹配容器types

对于inputtypes与输出types相匹配的简单情况(我所发现的并不是你所问的)要高一级。 而不是指定你的容器使用的typesT ,并试图专注于vector<T>等,只需指定容器本身的types:

 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; } 

更复杂:兼容的值types

由于您想尝试更改容器存储的项目types,因此需要使用模板模板参数,并修改返回的容器使用的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; } 

什么是不兼容的值types?

这只让我们在那里。 它工作正常与转换从signedunsigned但解决与T=intS=std::string ,并处理集,它试图实例std::set<std::string, std::less<int>, ...> ,因此不编译。

为了解决这个问题,我们想要取任意一组参数,并用UreplaceT实例,即使它们是其他模板参数的参数。 因此, std::set<int, std::less<int>>应该成为std::set<std::string, std::less<std::string>> ,等等。 这涉及到一些自定义模板元编程,正如其他答案所build议的。

模板元编程的救援

让我们创build一个模板,将其命名为replace_type ,并将其转换为U ,将K<T>K<U> 。 首先让我们来处理一般情况。 如果不是模板types,并且与T不匹配,则其types应保持为K

 template <typename K, typename ...> struct replace_type { using type = K; }; 

然后是专业化。 如果它不是模板types,并且与T匹配,则它的types将变成U

 template <typename T, typename U> struct replace_type<T, T, U> { using type = U; }; 

最后是recursion步骤来处理模板types的参数。 对于模板types参数中的每个types,相应地replacetypes:

 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::mapstd::unordered_map并不算太坏。 主要的问题是replace_type需要replace更多的types。 不仅有T – > Ureplace,还有std::pair<T, T2> – > std::pair<U, U2>replace。 这增加了对不需要的typesreplace的关注程度,因为在飞行中不止一种types。 这就是说,这是我发现的工作。 请注意,在testing中,我需要指定转换我的映射对的lambda函数的返回types:

 // 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 ::数组?

处理std::array增加了痛苦,因为它的模板参数不能在上面的模板中推导出来。 正如Jarod42指出的,这是由于它的参数包括值而不是types。 我已经通过添加专门化,并引入一个辅助contained_type ,为我提取T (注意,每个构造函数更好地写成更简单的typename Container::value_type ,适用于我在此讨论的所有types)。 即使没有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 。 虽然有可能做更多的专业化,但我会把这个作为一个模板汤练习给感兴趣的读者。 我个人会select在大多数情况下不支持std::array

查看累积的现场示例


充分的披露:虽然这个方法受到了阿里引用Kerrek SB的回答的影响,但是我并没有设法在Visual Studio 2013中工作,所以我自己构build了上述的替代scheme。 非常感谢Kerrek SB的部分原始答案仍然是必要的,以及施工人员和Jodod的鼓励和鼓励。

一些言论

下面的方法允许从标准库中转换任何types的容器( std::array有一个问题,见下文)。 容器的唯一要求是它应该使用默认的std::allocator类, std::lessstd::equal_tostd::hash函数对象。 所以我们有三组来自标准库的容器:

  1. 具有一个非默认模板types参数(值的types)的容器:

    • std::vectorstd::dequestd::liststd::forward_list ,[ std::valarray ]
    • std::queuestd::priority_queuestd::stack
    • std::setstd::unordered_set
  2. 具有两个非默认模板types参数的容器(键的types和值的types):

    • std::mapstd::multi_mapstd::unordered_mapstd::unordered_multimap
  3. 具有两个非默认参数的容器:types参数(值的types)和非types参数(大小):

    • std::array

履行

convert_container helper类将已知input容器types( InputContainer )和输出值types( OutputType )的types转换为输出容器types( typename convert_container<InputContainer, Output>::type ):

 template <class InputContainer, class OutputType> struct convert_container; // conversion for the first group of standard containers template <template <class...> class C, class IT, class OT> struct convert_container<C<IT>, OT> { using type = C<OT>; }; // conversion for the second group of standard containers template <template <class...> class C, class IK, class IT, class OK, class OT> struct convert_container<C<IK, IT>, std::pair<OK, OT>> { using type = C<OK, OT>; }; // conversion for the third group of standard containers template < template <class, std::size_t> class C, std::size_t N, class IT, class OT > struct convert_container<C<IT, N>, OT> { using type = C<OT, N>; }; template <typename C, typename T> using convert_container_t = typename convert_container<C, T>::type; 

transform_container函数实现:

 template < class InputContainer, class Functor, class InputType = typename InputContainer::value_type, class OutputType = typename std::result_of<Functor(InputType)>::type, class OutputContainer = convert_container_t<InputContainer, OutputType> > OutputContainer transform_container(const InputContainer& ic, Functor f) { OutputContainer oc; std::transform(std::begin(ic), std::end(ic), std::inserter(oc, oc.end()), f); return oc; } 

使用示例

查看以下转换的实例 :

  • std::vector<int> -> std::vector<std::string>
  • std::set<int> -> std::set<double>
  • std::map<int, char> -> std::map<char, int>

问题

std::array<int, 3> -> std::array<double, 3>转换不能编译,因为std::array没有insert所需的方法,因为std::inserter )。 transform_container函数不应该因为这个原因使用以下容器: std::forward_liststd::queuestd::priority_queuestd::stack ,[ std::valarray ]。

这样做一般来说是相当困难的。

首先,考虑std::vector<T, Allocator=std::allocator<T>> ,假设你的函子转换T->U 我们不仅需要映射第一个types参数,而且实际上我们应该使用Allocator<T>::rebind<U>来获取第二个参数。 这意味着我们需要知道第二个参数是一个分配器…或者我们需要一些机制来检查它是否有rebind成员模板并使用它。

接下来,考虑std::array<T, N> 。 在这里,我们需要知道第二个参数应该复制到我们的std::array<U, N> 。 也许我们可以在不改变的情况下使用非types参数,带有rebind成员模板的rebindtypes参数,并用Ureplace文字T

现在, std::map<Key, T, Compare=std::less<Key>, Allocator=std::allocator<std::pair<Key,T>>> 。 我们应该没有改变的Key ,用U代替TCompare没有改变,并重新std::allocator<std::pair<Key, U>>std::allocator<std::pair<Key, U>> Allocator 。 这有点复杂。

所以…你可以没有任何灵活性的生活? 你很高兴忽略关联容器,并假设你的转换输出容器的默认分配器是可以的吗?

主要困难是以某种方式从Conainer<T>获取容器typesContainer 。 我从模板元编程中无耻地窃取了代码(特征为:将特定的模板分解成T <T2,T3 N,T4,…>types ,特别是Kerrek SB的答案 (接受的答案),就像我不熟悉模板元编程。

 #include <algorithm> #include <cassert> #include <type_traits> // stolen from Kerrek SB's answer template <typename T, typename ...> struct tmpl_rebind { typedef T type; }; template <template <typename ...> class Tmpl, typename ...T, typename ...Args> struct tmpl_rebind<Tmpl<T...>, Args...> { typedef Tmpl<Args...> type; }; // end of stolen code template <typename Container, typename Func, typename TargetType = typename std::result_of<Func(typename Container::value_type)>::type, typename NewContainer = typename tmpl_rebind<Container, TargetType>::type > NewContainer convert(const Container& c, Func f) { NewContainer nc; std::transform(std::begin(c), std::end(c), std::inserter(nc, std::end(nc)), f); return nc; } int main() { std::vector<int> vi{ 1, 2, 3, 4, 5 }; auto vs = convert(vi, [] (int i) { return std::to_string(i); }); assert( vs == std::vector<std::string>( {"1", "2", "3", "4", "5"} ) ); return 0; } 

我已经testing了这个代码与海湾合作委员会4.7.2和铛3.5,并按预期工作。

正如Yakk所指出的那样 ,这个代码有几个注意事项: “…应该用rebind来代替所有的参数还是第一个参数呢?不确定,在后面的参数中是否应该用T1代替T0std::map<T0, std::less<T0>> – > std::map<T1, std::less<T1>> 我也看到上面的代码陷阱(例如,如何处理不同的分配器,也见无用答案 )。

不过,我相信上面的代码对于简单的用例已经很有用了。 如果我们正在编写一个效用函数来提高效率,那么我会更有动力进一步调查这些问题。 但已经有了一个可以接受的答案,所以我认为这个案子已经结束


非常感谢Constructor,Dyp和Yakk指出我的错误/错过了改进的机会。