Triangularizing a tuple - c++

I’ve been trying to upgrade (and slightly adapt) this solution from 2012 using modern features of C++.
My goal is actually slightly simpler than in that question; I’d like this:
triangularize_t<T0, T1, ..., TN-1, TN>
To be equivalent to this:
std::tuple<
std::tuple<>,
std::tuple<T0>,
std::tuple<T0, T1>,
...
std::tuple<T0, T1, ..., TN-1>
>
Such that std::tuple_element_t<N, result> has N elements. The type TN should not appear anywhere in the output.
Here’s what I’ve worked up to so far:
template <class _Tuple, class _Seq>
struct _tuple_head;
template <class _Tuple, size_t... _N>
struct _tuple_head<_Tuple, std::index_sequence<_N...>> {
using type = std::tuple<std::tuple_element_t<_N, _Tuple>...>;
};
template <class _Tuple, class _Seq>
struct _triangularize_impl;
template <class _Tuple, size_t... _N>
struct _triangularize_impl<_Tuple, std::index_sequence<_N...>> {
using type = std::tuple<typename _tuple_head<_Tuple, std::make_index_sequence<_N>>::type...>;
};
template <class... _Pack>
struct triangularize {
using type = _triangularize_impl<std::tuple<_Pack...>, std::index_sequence_for<_Pack...>>;
};
template <class... _Pack>
using triangularize_t = typename triangularize<_Pack...>::type;
However, it doesn’t quite seem to be expanding the way I’d expect it to. Using triangularize_t<int, float> as a test, error messages seem to report that the output for std::get<0> and 1 return these types (identical for some reason).
_triangularize_impl<tuple<std::__1::tuple<int, float> >, __make_integer_seq<integer_sequence, unsigned long, 1UL> >
_triangularize_impl<tuple<std::__1::tuple<int, float> >, __make_integer_seq<integer_sequence, unsigned long, 1UL> >
What have I missed here?

Maybe someone can make it in a simpler way... but what about as follows?
template <typename T, std::size_t ... Is>
auto gtt_helper (std::index_sequence<Is...>)
-> std::tuple<std::tuple_element_t<Is, T>...>;
template <typename ... Ts, std::size_t ... Is>
auto getTriTuple (std::index_sequence<Is...>)
-> std::tuple<decltype(gtt_helper<std::tuple<Ts...>>
(std::make_index_sequence<Is>{}))...>;
template <typename ... Ts>
using triTuple
= decltype(getTriTuple<Ts...>(std::index_sequence_for<Ts...>{}));
The following is a full compiling C++14 example
#include <type_traits>
#include <utility>
#include <tuple>
template <typename T, std::size_t ... Is>
auto gtt_helper (std::index_sequence<Is...>)
-> std::tuple<std::tuple_element_t<Is, T>...>;
template <typename ... Ts, std::size_t ... Is>
auto getTriTuple (std::index_sequence<Is...>)
-> std::tuple<decltype(gtt_helper<std::tuple<Ts...>>
(std::make_index_sequence<Is>{}))...>;
template <typename ... Ts>
using triTuple
= decltype(getTriTuple<Ts...>(std::index_sequence_for<Ts...>{}));
int main ()
{
using T0 = triTuple<char, int, long, long long>;
using T1 = std::tuple<std::tuple<>,
std::tuple<char>,
std::tuple<char, int>,
std::tuple<char, int, long>>;
static_assert( std::is_same<T0, T1>::value, "!" );
}
To respond to your question ("What have I missed here?"), you have missed a typename and a ::type in triangularize
It seems to me that the right version should be
template <class... _Pack>
struct triangularize {
// ..........VVVVVVVV add typename
using type = typename _triangularize_impl<std::tuple<_Pack...>,
std::index_sequence_for<_Pack...>>::type ;
// and add ::type ..........................................................^^^^^^
};
Unfortunately, your (corrected) code seems to works with clang++ but not with g++; I suspect a g++ bug but I'm not sure.

With Boost.Mp11 this is... unfortunately not a one-liner. It takes a couple lines instead.
We define one function to perform a single action: given a list of everything and the next element, append that one (that is, this takes us from the Nth solution to the N+1st solution):
template <typename L, typename T>
using add_one = mp_push_back<L, mp_push_back<mp_back<L>, T>>;
And now fold over that - which just applies that binary function for each argument in turn:
template <typename... Ts>
using triangularize_t = mp_fold<mp_list<Ts...>, tuple<tuple<>>, add_one>;
And check that it's correct:
static_assert(std::is_same_v<triangularize_t<>,
tuple<tuple<>>>);
static_assert(std::is_same_v<triangularize_t<int>,
tuple<tuple<>, tuple<int>>>);
static_assert(std::is_same_v<triangularize_t<int, char>,
tuple<tuple<>, tuple<int>, tuple<int, char>>>);
We can generalize this to work on any class template instead of solely tuple by changing triangularize to use an input list and deduce its initial value from the input argument:
template <typename L>
using triangularize_t = mp_fold<L, mp_push_back<mp_clear<L>, mp_clear<L>>, add_one>;
Which also allows:
static_assert(std::is_same_v<triangularize_t<mp_list<int, char>>,
mp_list<mp_list<>, mp_list<int>, mp_list<int, char>>>);
Or whatever other lists you might want to use (notably not variant, since variant<> is ill-formed).

With Boost.Mp11 this is a one-liner. I just didn't try hard enough last time. Also this solution matches OP's exact specification:
template <typename... Ts>
using triangularize_t =
mp_transform_q<
mp_bind_front<mp_take, std::tuple<Ts...>>,
mp_rename<mp_iota_c<sizeof...(Ts)>, std::tuple>
>;
Lemme explain what this does, assuming Ts... is <int, char>.
mp_iota_c<sizeof...(Ts)> gives the sequence mp_list<mp_int<0>, mp_int<1>>.
mp_rename swaps out one "list" type for another, in this case mp_list for std::tuple so you get std::tuple<mp_int<0>, mp_int<1>>.
mp_bind_front<mp_take, std::tuple<Ts...>> creates a metafunction on the fly that will take an argument and apply it to mp_take on the full tuple<Ts...>. mp_take takes the first N things from the given list. If we passed in mp_int<1> to this, on our initial tuple<int, char>, we'd get tuple<int>.
mp_transform_q calls the provided metafunction on each element in the list. We take our tuple<mp_int<0>, mp_int<1>> and expand it out into tuple<mp_take<tuple<int, char>, mp_int<0>>, mp_take<tuple<int, char>, mp_int<1>>> which is tuple<tuple<>, tuple<int>>. As desired.
To change this into my other answer (which triangularizes <int> into tuple<tuple<>, tuple<int>>), we can change sizeof...(Ts) into sizeof...(Ts)+1.
To extend this to support any list type (not just tuple), we can change the metafunction here to take a list instead of a pack and use the provided list type as a solution. In some respects, this makes the solution easier:
template <typename L>
using triangularize_t =
mp_transform_q<
mp_bind_front<mp_take, L>,
mp_append<mp_clear<L>, mp_iota<mp_size<L>>>
>;
template <typename... Ts>
using triangularize_t = triangularize_list<std::tuple<Ts...>>;
The awkward part here is the mp_append<mp_clear<L>, mp_iota<mp_size<L>>>. Basically, we need the sequence list to have the same list type as the original list. Before, we could use mp_rename because we know we needed a tuple. But now, we don't have the list as a class template - just have an instance of it. There might be a better way to do this than mp_append<mp_clear<L>, U>... but this is what I have so far.

We can avoid having to use any index_sequences by utilizing partial specialization with multiple parameter packs.
#include <tuple>
template <typename Tuple, typename LastTuple, typename First, typename... Rest>
struct triangulate;
template <typename Tuple, typename LastTuple, typename Last>
struct triangulate<Tuple, LastTuple, Last> {
using type = Tuple;
};
template <typename First, typename Second, typename... TupleTypes, typename... LastTupleTypes, typename... Rest>
struct triangulate<std::tuple<TupleTypes...>, std::tuple<LastTupleTypes...>, First, Second, Rest...> {
using next = std::tuple<LastTupleTypes..., First>;
using type = typename triangulate<std::tuple<TupleTypes..., next>, next, Second, Rest...>::type;
};
template <typename... T>
using triangularize_t = typename triangulate<std::tuple<std::tuple<>>, std::tuple<>, T...>::type;
Passing the end product as the first parameter (std::tuple<std::tuples...>>), and the second parameter is the last std::tuple<...> we used.
We then recursively add the next parameter to the last tuple, and add that tuple to the end of the end-result.

Related

Does there exist a type level left-fold metafunction in the standard library?

If a,b,c,.. denote types then let (a,b) be the type std::pair<a,b> I am looking for the map, F, such that
F : ((((a,b),c),d),...))) -> std::tuple<a,b,c,d...>
Does this exist under some existing name in the standard library? If not, is there another library
in which it does or is this easily implemented and I'm just too dumb to know how to do it?
I think this one basically has to be recursive. There is no such thing in the standard library, and for once I can't come up with a Boost.Mp11 one-liner.
namespace impl {
template <typename T>
struct unfold_t {
using type = std::tuple<T>;
};
template <typename A, typename B>
struct unfold_t<std::pair<A, B>> {
using type = mp_push_back<typename unfold_t<A>::type, B>;
};
}
template <typename T>
using unfold = typename impl::unfold_t<T>::type;
With an assist from T.C., the newest edition of Mp11 (1.73) has an algorithm named mp_iterate which we can use thusly.
Given std::pair<std::pair<X, Y>, Z>, if we apply mp_first repeatedly (as R) we get the sequence:
std::pair<std::pair<X, Y>, Z>
std::pair<X, Y>
X
Then, if we apply mp_second to that sequence, we get:
Z
Y
ill-formed
That's pretty close. We do need that X. So we need a more complex metafunction for F: we need to get the second value if possible. There's a metafunction for that:
template <typename L>
using second_or_self = mp_eval_or<L, mp_second, L>;
And now mp_iterate<T, second_or_self, mp_first> gives us mp_list<Z, Y, X>. All we need to do at that point is reverse it and turn it into a std::tuple:
template <typename L>
using unfold = mp_rename<mp_reverse<
mp_iterate<L, second_or_self, mp_first>>,
std::tuple>;
Now that Boost.173 is on compiler-explorer, demo.
Or, I suppose, if you really hate your readers, you can make this a single alias:
template <typename L>
using unfold = mp_rename<mp_reverse<
mp_iterate_q<L,
mp_bind<mp_eval_or_q, _1, mp_quote<mp_second>, _1>,
mp_quote<mp_first>>>,
std::tuple>;
I like the solution by #Barry, and in particular the name unfold for this meta-function. However, I think you can implement this quite easily using partial specializations, without using boost.Mp11, and you might find this easier to understand.
First, provide the base case for a type that's already completely unfolded into a tuple:
template <typename ...Ts>
struct unfold_impl { using type = std::tuple<Ts...>; };
and then provide a specialization if there's still a pair left to unfold:
template <typename A, typename B, typename ...Ts>
struct unfold_impl<std::pair<A,B>, Ts...> : unfold_impl<A, B, Ts...> {};
and finally provide a convenience alias template:
template <typename ...Ts>
using unfold = typename unfold_impl<Ts...>::type;
and that's it.
You can check that it works, like this:
static_assert(
std::is_same_v<
unfold<std::pair<std::pair<std::pair<int, long>, char>, double>>,
std::tuple<int, long, char, double>
>);
Here's a demo

C++ Take a union of the parameters in a parameter pack of variadic templates?

I've been writing a bit of code in C++ that involves a lot of metaprogramming; the code models a task that is defined at runtime by some series of nodes that take a number of inputs and, when run, produce some number of outputs. Inputs and outputs are linked in a directed graph. There's a bounded number of node types, which I'm storing in a variant, and each node class defines a static constexpr variable that, more or less, lists what types are input and output from that node. I'd like to be able to take a list of the classes of nodes and convert it into a list of the classes of data that are acted upon by the nodes.
To be more explicit, if I have a template that purely tracks some list of classes at compile-time like this:
template<class... Args>
struct ClassList{};
I want some sort of template that transforms list of lists like this:
ClassList<ClassList<int,int,double>, ClassList<double,char>, ClassList<char, char> >
into the union of the inner lists:
ClassList<int,double,char>
with no special requirements for order - except that each type appearing in the original lists appears exactly once in the final list - and if possible, I'd like to do this in a way that won't cause my compiler to explode. I know that I could, in theory, write a whole bunch of recursive templates that would join the lists into one and then remove duplicates, but that solution sounds a bit unsavory. Is there a better way?
A solution that filters types before adding them into the final list:
template<class... Ts>
struct class_list {};
template<class... Ts>
struct make_unique {
using type = class_list<Ts...>;
};
template<class... Ts>
struct make_unique<class_list<>, Ts...> : make_unique<Ts...>{};
template<class U, class... Us, class... Ts>
struct make_unique<class_list<U, Us...>, Ts...>
: std::conditional_t<
(std::is_same_v<U, Us> || ...) || (std::is_same_v<U, Ts> || ...),
make_unique<class_list<Us...>, Ts... >,
make_unique<class_list<Us...>, Ts..., U>> {};
template<class... Ts>
using make_unique_class_list = typename make_unique<Ts...>::type;
using T = make_unique_class_list<class_list<int, int, double>,
class_list<double, char>, class_list<char, char>>;
static_assert(std::is_same_v<T, class_list<int, double, char>>);
Note that the following similarly looking solution also works but can be too slow, because it instantiates many unnecessary templates:
template<class U, class... Us, class... Ts>
struct make_unique<class_list<U, Us...>, Ts...>
: std::conditional<
(std::is_same_v<U, Us> || ...) || (std::is_same_v<U, Ts> || ...),
typename make_unique<class_list<Us...>, Ts... >::type,
typename make_unique<class_list<Us...>, Ts..., U>::type> {};

trailing return type of deduction guide is not a specialization

I'm trying to do an advanced class template argument deduction by using the new deduction guides from c++17. Unfortunately, it looks like you can only use simple template declarations after the ->, but I need a helper struct to determine the resulting type.
My use case is this one: I have a variadic template class that takes an arbitrary amount of different types. For one constructor I want to specify every single one, for another ctor I want to specify only one type and replicate it N times.
To access this N in the deduction guide I introduced a new type:
template<size_t N>
struct Replicate { };
The class I have is similar this one:
template<typename... Foos>
struct Foo {
// [ ... ] member std::tuple<Foos...>
// ctor 1: give values for all types (easy to deduce)
Foo(Foos&&... args /* rvalue ref, NOT forwarding */) { };
// ctor 2: give one value and N, result is N copies of this value.
// takes Replicate<N> as parameter to aid the deduction.
template<typename T, size_t N>
Foo(const T& t, Replicate<N>) { };
};
The usage would be like this:
Foo f1{1, 2, 3.0}; // deduce Foo<int, int, double>;
Foo f2{8, Replicate<4>{}}; // deduce Foo<int, int, int, int>;
The deduction guide for the first one is straight forward:
template<typename... Ts>
Foo(Ts&&...) -> Foo<Ts...>;
It gets problematic with the second (ctor 2) deduction guide. First I need a helper struct to create Foo<T, T, ... /* total N times */, T> from T and N.
template<typename, typename, typename>
struct ExpandNTimes;
template<typename T, size_t... Is>
struct ExpandNTimes<T, std::index_sequence<Is...>> {
template<size_t> using NthType = T;
using Type = Foo<NthType<Is>...>;
};
Then in the deduction guide I want to utilize the helper to deduce the correct type. I cant directly use Foo<something> as there is no kind of "in place parameter pack creation", therefore the helper struct.
template<typename T, size_t N>
Foo(const T&, Replicate<N>) -> typename ExpandNTimes<T, std::make_index_sequence<N>>::Type;
Unfortunately this results int an error similar to this one:
error: trailing return type of 'typename ExpandNTimes<T, /* std things */>::Type' deduction guide is not a specialization of ‘Foo<Ts>’
Is there any way to work around this issue?
This is impossible with class template argument deduction - both template names must be the same, and the thing after the -> must be a simple-template-id. This doesn't leave any room for template shenanigans.
But nothing prevents you from doing the thing that class template argument deduction is intended to replace: factory functions:
template <typename T, size_t N>
typename ExpandNTimes<T, std::make_index_sequence<N>>::Type
makeFoo(T const&, Repliace<N>);
This is doable if you can change Replicate's definition to embed a pack into a base class:
template<class> struct ReplicateBase {};
template<size_t N> struct Replicate : ReplicateBase<std::make_index_sequence<N>> {};
template<size_t, class T> using Meow = T;
template<typename T, size_t... Ns>
Foo(const T&, ReplicateBase<std::index_sequence<Ns...>>) -> Foo<Meow<Ns, T>...>;
Then it's a "simple" matter of constraining everything else to not compete with this guide when passed a Replicate:
Foo(Foos&&... args) { } and template<typename... Ts> Foo(Ts&&...) -> Foo<Ts...>; (are you sure you want to deduce references when passed lvalues?) should be constrained to when Foos/Ts aren't Replicates
template<typename T, size_t N> Foo(const T& t, Replicate<N>); needs to be constrained to prevent it from being used to deduce an empty pack (e.g., to when sizeof...(Foos) == N)

Making a cpp sorted tuple

Here's the code for std::make_tuple in the standard library.
template<typename... _Elements>
inline tuple<typename __decay_and_strip<_Elements>::__type...>
make_tuple(_Elements&&... __args)
{
typedef tuple<typename __decay_and_strip<_Elements>::__type...>
__result_type;
return __result_type(std::forward<_Elements>(__args)...);
}
What I would like to do, is to sort __args before creating the tuple, presumably with std::sort(..., Compare comp) where the user passes in an appropriate comparator that can be used to sort whatever type things end up in __args.
However, I'm relatively new to cpp, I don't understand half of the code in this function, and the std::sort needs an argument for the end of __args, and I'm not sure how to derive that.
Also please explain the typename __decay_and_strip<_Elements>::__type... and _Elements&&... bits...
EDIT Since for arbitrary type combinations the return type would then be unknown at compile time, the generic case seems to be not possible. Supposing all the same type, then, and we replace ..._Elements with T, I'm still unsure how to get the ".end()" of __args for use in std::sort
This can be done if the tuple type arguments are homogeneous. (We can't sort non-homogeneous types because that would require rearrangement of the types themselves, and that's not something you can do at compile time.1)
Assuming homogeneous types, the solution basically boils down to this:
Throw the arguments into an array.
Sort the array.
Make a tuple from the array contents.
This isn't too hard. First we need the indices trick to index our array (for step 3 -- you can use std::index_sequence instead if you have C++14):
template <std::size_t... Is>
struct indices {};
template <std::size_t N, std::size_t... Is>
struct build_indices
: build_indices<N-1, N-1, Is...> {};
template <std::size_t... Is>
struct build_indices<0, Is...> : indices<Is...> {};
Then we need a way to peel off the first type from the parameter pack to declare our array (for step 1). As a bonus, we'll have it check to make sure all the types are the same:
template <typename...>
struct pack_type;
template <typename Head>
struct pack_type<Head>
{
using type = Head;
};
// Will fail deduction on a non-homogeneous pack.
template <typename Head, typename... Tail>
struct pack_type<Head, Head, Tail...> : pack_type<Head, Tail...> {};
Finally, our sorter implementation with a helper to build the indices pack:
template <std::size_t... I, typename Comparer, typename... Ts>
std::tuple<Ts...> make_sorted_tuple_impl(indices<I...>, Comparer const &c, Ts && ...args)
{
typename pack_type<Ts...>::type values[sizeof...(Ts)] = { std::forward<Ts>(args)... };
std::sort(std::begin(values), std::end(values), c);
return std::make_tuple(std::forward<Ts>(values[I])...);
}
// Special case to handle empty tuples.
template <typename Comparer>
std::tuple<> make_sorted_tuple_impl(indices<>, Comparer const &)
{
return std::tuple<>();
}
template <typename Comparer, typename... Ts>
std::tuple<Ts...> make_sorted_tuple(Comparer const &c, Ts && ...args)
{
return make_sorted_tuple_impl(build_indices<sizeof...(Ts)>(), c, std::forward<Ts>(args)...);
}
See it run.
Also please explain the typename __decay_and_strip<_Elements>::__type... and _Elements&&... bits...
I'm not going to explain the first because identifiers containing __ are reserved by the C++ implementation, so this __decay_and_strip is an implementation detail specific to this particular C++ implementation.
_Elements&&... is a pack of rvalue references. This allows the arguments to be perfectly forwarded to the std::tuple constructor.
1 I lied. You can do it if the values and the compare function are constexpr, but the code to pull it off will be huge and not worth the time to write.

The std::transform-like function that returns transformed container

I'm trying to implement a function similar to std::transform algorithm but instead of taking the output iterator by an argument I want to create and return a container with transformed input elements.
Let's say that it's named transform_container and takes two arguments: container and functor. It should return the same container type but possibly parametrized by a different element type (the Functor can return element of different type).
I'd like to use my function as in the example below:
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.}));
I was able two write two functions — one for std::set and one for std::vector — that seem to work properly. They are identical, except of the container typename. Their code is listed below.
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;
}
However, when I attempted to merge them into a single general function that works with any container, I encountered numerous issues. The set and vector are class templates, so my function template must take a template template parameter. Moreover, set and vector templates have a different number of type parameters that needs to be properly adjusted.
What is the best way to generalize the two function templates above into a function that works with any compatible container type?
Simplest cases: matching container types
For the simple case where the input type matches the output type (which I've since realized is not what you're asking about) go one level higher. Instead of specifying the type T that your container uses, and trying to specialize on a vector<T>, etc., just specify the type of the container itself:
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;
}
More complexity: compatible value types
Since you want to try to change the item type stored by the container, you'll need to use a template template parameter, and modify the T to that which the returned container uses.
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;
}
What of incompatible value types?
This only gets us partway there. It works fine with a transform from signed to unsigned but, when resolving with T=int and U=std::string, and handling sets, it tries to instantiate std::set<std::string, std::less<int>, ...> and thus doesn't compile.
To fix this, we want to take an arbitrary set of parameters and replace instances of T with U, even if they are the parameters to other template parameters. Thus std::set<int, std::less<int>> should become std::set<std::string, std::less<std::string>>, and so forth. This involves some custom template meta programming, as suggested by other answers.
Template metaprogramming to the rescue
Let's create a template, name it replace_type, and have it convert T to U, and K<T> to K<U>. First let's handle the general case. If it's not a templated type, and it doesn't match T, its type shall remain K:
template <typename K, typename ...>
struct replace_type { using type = K; };
Then a specialization. If it's not a templated type, and it does match T, its type shall become U:
template <typename T, typename U>
struct replace_type<T, T, U> { using type = U; };
And finally a recursive step to handle parameters to templated types. For each type in a templated type's parameters, replace the types accordingly:
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 ...>;
};
And finally update transform_container to use 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;
}
Is this complete?
The problem with this approach is it is not necessarily safe. If you're converting from Container<MyCustomType> to Container<SomethingElse>, it's likely fine. But when converting from Container<builtin_type> to Container<SomethingElse> it's plausible that another template parameter shouldn't be converted from builtin_type to SomethingElse. Furthermore, alternate containers like std::map or std::array bring more problems to the party.
Handling std::map and std::unordered_map isn't too bad. The primary problem is that replace_type needs to replace more types. Not only is there a T -> U replacement, but also a std::pair<T, T2> -> std::pair<U, U2> replacement. This increases the level of concern for unwanted type replacements as there's more than a single type in flight. That said, here's what I found to work; note that in testing I needed to specify the return type of the lambda function that transformed my map's pairs:
// 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 ...
>;
};
What about std::array?
Handling std::array adds to the pain, as its template parameters cannot be deduced in the template above. As Jarod42 notes, this is due to its parameters including values instead of just types. I've gotten partway by adding specializations and introducing a helper contained_type that extracts T for me (side note, per Constructor this is better written as the much simpler typename Container::value_type and works for all types I've discussed here). Even without the std::array specializations this allows me to simplify my transform_container template to the following (this may be a win even without support for 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
}
However the current implementation of transform_container uses std::inserter which does not work with std::array. While it's possible to make more specializations, I'm going to leave this as a template soup exercise for an interested reader. I would personally choose to live without support for std::array in most cases.
View the cumulative live example
Full disclosure: while this approach was influenced by Ali's quoting of Kerrek SB's answer, I didn't manage to get that to work in Visual Studio 2013, so I built the above alternative myself. Many thanks to parts of Kerrek SB's original answer are still necessary, as well as to prodding and encouragement from Constructor and Jarod42.
Some remarks
The following method allows to transform containers of any type from the standard library (there is a problem with std::array, see below). The only requirement for the container is that it should use default std::allocator classes, std::less, std::equal_to and std::hash function objects. So we have 3 groups of containers from the standard library:
Containers with one non-default template type parameter (type of value):
std::vector, std::deque, std::list, std::forward_list, [std::valarray]
std::queue, std::priority_queue, std::stack
std::set, std::unordered_set
Containers with two non-default template type parameters (type of key and type of value):
std::map, std::multi_map, std::unordered_map, std::unordered_multimap
Container with two non-default parameters: type parameter (type of value) and non-type parameter (size):
std::array
Implementation
convert_container helper class convert types of known input container type (InputContainer) and output value type (OutputType) to the type of the output container(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 function implementation:
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;
}
Example of use
See live example with the following conversions:
std::vector<int> -> std::vector<std::string>,
std::set<int> -> std::set<double>,
std::map<int, char> -> std::map<char, int>.
Problems
std::array<int, 3> -> std::array<double, 3> conversion doesn't compile because std::array haven't insert method which is needed due to std::inserter). transform_container function shouldn't also work for this reason with the following containers: std::forward_list, std::queue, std::priority_queue, std::stack, [std::valarray].
Doing this in general is going to be pretty hard.
First, consider std::vector<T, Allocator=std::allocator<T>>, and let's say your functor transforms T->U. Not only do we have to map the first type argument, but really we ought to use Allocator<T>::rebind<U> to get the second. This means we need to know the second argument is an allocator in the first place ... or we need some machinery to check it has a rebind member template and use it.
Next, consider std::array<T, N>. Here we need to know the second argument should be copied literally to our std::array<U, N>. Perhaps we can take non-type parameters without change, rebind type parameters which have a rebind member template, and replace literal T with U?
Now, std::map<Key, T, Compare=std::less<Key>, Allocator=std::allocator<std::pair<Key,T>>>. We should take Key without change, replace T with U, take Compare without change and rebind Allocator to std::allocator<std::pair<Key, U>>. That's a little more complicated.
So ... can you live without any of that flexibility? Are you happy to ignore associative containers and assume the default allocator is ok for your transformed output container?
The major difficulty is to somehow get the container type Container from Conainer<T>. I have shamelessly stolen the code from template metaprogramming: (trait for?) dissecting a specified template into types T<T2,T3 N,T4, ...>, in particular, Kerrek SB's answer (the accepted answer), as I am not familiar with template metaprogramming.
#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;
}
I have tested this code with gcc 4.7.2 and clang 3.5 and works as expected.
As Yakk points out, there are quite a few caveats with this code though: "... should your rebind replace all arguments, or just the first one? Uncertain. Should it recursively replace T0 with T1 in later arguments? Ie std::map<T0, std::less<T0>> -> std::map<T1, std::less<T1>>?" I also see traps with the above code (e.g. how to deal with different allocators, see also Useless' answer).
Nevertheless, I believe the above code is already useful for simple use cases. If we were writing a utility function to be submitted to boost, then I would be more motivated to investigate these issues further. But there is already an accepted answer so I consider the case closed.
Many thanks to Constructor, dyp and Yakk for pointing out my mistakes / missed opportunities for improvements.
I wrote a blog post to solve a similar problem recently. Using templates and the iterator interface was the route I chose to follow.
for_each:
To cut down on the amount of boilerplate, we're going to create a using clause that allows us to grab the type contained within an iterator:
template <typename IteratorType>
using ItemType = typename std::iterator_traits<typename IteratorType::iterator>::value_type;
With that in place, we can implement a helper function for_each like so:
template <typename IteratorType>
void for_each(IteratorType &items, std::function<void(ItemType<IteratorType> const &item)> forEachCb)
{
for (typename IteratorType::iterator ptr = items.begin(); ptr != items.end(); ++ptr)
forEachCb(*ptr);
}
transform_container:
Finally transform_container, could be implemented like so:
template <typename IteratorType, typename ReturnType>
ReturnType transform_container(IteratorType &items, std::function<ItemType<ReturnType>(ItemType<IteratorType> const &item)> mapCb)
{
ReturnType mappedIterator;
for_each<IteratorType>(items, [&mappedIterator, &mapCb](auto &item) { mappedIterator.insert(mappedIterator.end(), mapCb(item)); });
return mappedIterator;
}
Which will allow us to call your two examples in the following way:
std::vector<int> vi{ 1, 2, 3, 4, 5 };
auto vs = transform_container<std::vector<int>, std::vector<std::string>>(vi, [](int i){return std::to_string(i);});
assert(vs == std::vector<std::string>({"1", "2", "3", "4", "5"}));
std::set<int> si{ 5, 10, 15 };
auto sd = transform_container<std::set<int>, std::set<double>>(si, [] (int i) { return i / 2.; });
assert(sd == std::set<double>({5/2., 10/2., 15/2.}));
My blog post also goes into a little more detail if that's helpful.