类似std :: transform的函数返回转换的容器
我想实现一个类似于std::transform
algorithm的函数,而不是通过我想创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; }
但是,当我试图将它们合并成一个与任何容器一起工作的通用函数时,我遇到了很多问题。 set
和vector
是类模板,所以我的函数模板必须采用模板模板参数。 而且,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?
这只让我们在那里。 它工作正常与转换从signed
到unsigned
但解决与T=int
和S=std::string
,并处理集,它试图实例std::set<std::string, std::less<int>, ...>
,因此不编译。
为了解决这个问题,我们想要取任意一组参数,并用U
replaceT
实例,即使它们是其他模板参数的参数。 因此, 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::map
和std::unordered_map
并不算太坏。 主要的问题是replace_type
需要replace更多的types。 不仅有T
– > U
replace,还有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::less
, std::equal_to
和std::hash
函数对象。 所以我们有三组来自标准库的容器:
-
具有一个非默认模板types参数(值的types)的容器:
-
std::vector
,std::deque
,std::list
,std::forward_list
,[std::valarray
] -
std::queue
,std::priority_queue
,std::stack
-
std::set
,std::unordered_set
-
-
具有两个非默认模板types参数的容器(键的types和值的types):
-
std::map
,std::multi_map
,std::unordered_map
,std::unordered_multimap
-
-
具有两个非默认参数的容器: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_list
, std::queue
, std::priority_queue
, std::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参数,并用U
replace文字T
?
现在, std::map<Key, T, Compare=std::less<Key>, Allocator=std::allocator<std::pair<Key,T>>>
。 我们应该没有改变的Key
,用U
代替T
, Compare
没有改变,并重新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
代替T0
? std::map<T0, std::less<T0>>
– > std::map<T1, std::less<T1>>
? 我也看到上面的代码陷阱(例如,如何处理不同的分配器,也见无用答案 )。
不过,我相信上面的代码对于简单的用例已经很有用了。 如果我们正在编写一个效用函数来提高效率,那么我会更有动力进一步调查这些问题。 但已经有了一个可以接受的答案,所以我认为这个案子已经结束
非常感谢Constructor,Dyp和Yakk指出我的错误/错过了改进的机会。