Suppose I've got a tuple type std::tuple<x,y,z>
, or maybe std::tuple<a,b>
. I'd like a general purpose way to transform the types in my tuple, e.g. "functionize" to get
std::tuple<std::function<void(x)>,
std::function<void(y)>,
std::function<void(z)>>
or maybe get storage for shared_pointers like
std::tuple<std::shared_pointer<a>,
std::shared_pointer<b>>
How would I achieve this with C++17? C++20 answers interesting but not applicable in my current situation.
Motivation: I've got a class that's going to be parameterized by a list of arbitrary and not necessarily unique types, and would like to have a std::function member for each type in the list.
Template specialization is probably the easiest way.
template <typename T>
struct functionize;
template <typename... Ts>
struct functionize<std::tuple<Ts...>> {
using type = std::tuple<std::function<void(Ts)>...>;
}
using MyTuple = std::tuple<int, double, char>;
using MyTupleFunctionized = typename functionize<MyTuple>::type;
To make it more generic, you can accept a template template parameter to apply to the pack.
template <typename Tuple, template <typename> typename Component>
struct transform_tuple;
template <typename... Ts, template <typename> typename Component>
struct transform_tuple<std::tuple<Ts...>, Component> {
using type = std::tuple<Component<Ts>...>;
}
using MyTuple = std::tuple<int, double, char>;
using MyTransformedTuple = typename transform_tuple<MyTuple, std::shared_ptr>::type;
The more general solution works best with c++17
or later. Prior to that it would only match on a template with exactly 1 parameter. After c++17
it would also be able to match on something like std::vector
which has 2 template parameters where the second one has a default argument.
Edit:
As pointed out in the comments by @Jarod42 and @Caleth we can improve on this a bit more.
template <typename Tuple, template <typename...> typename Component>
struct transform_tuple;
A parameter pack for the template template parameter allows us to pass something like a std::vector
in from c++11
and forward. That only leaves issues if we want to pass something that mixes type and non-type parameters, like std::array
.
We can partially solve that issue by using template aliases.
template <typename T>
using FixedArray10 = std::array<T, 10>;
using MyTransformedTuple = typename transform_tuple<MyTuple, FixedArray10>::type;