std::initializer_list alternative - c++

I am trying to initialize my custom vector object, but without using std::initializer_list. I am doing something like this:
template <typename T, std::size_t N>
struct vector
{
template<std::size_t I = 0, typename ...Tp>
typename std::enable_if<I == sizeof...(Tp), void>::type
unpack_tuple(std::tuple<Tp...> const& t)
{
}
template<std::size_t I = 0, typename ...Tp>
typename std::enable_if<I != sizeof...(Tp), void>::type
unpack_tuple(std::tuple<Tp...> const& t)
{
store[I] = std::get<I>(t);
unpack_tuple<I + 1, Tp...>(t);
}
template<typename ...U>
vector(U&&... args,
typename std::enable_if<std::is_scalar<U...>::value, void>::type* = 0)
{
unpack_tuple(std::forward_as_tuple(std::forward<U>(args)...));
}
T store[N];
};
but the compiler does not grok the constructor unless I remove the std::enable_if argument, which I need (as I don't want non-scalar arguments). Does there exist a solution?

std::is_scalar<U...>::value
The problem lies with the fact that is_scalar only takes a single type argument. You need to write a wrapper that combines multiple boolean values. I also wonder why you use perfect forwarding if you only want scalar types anyways - just pass them by-value. This way, you also don't need to worry about U being deduced as a reference when you get passed an lvalue.
#include <type_traits>
template<bool B>
using bool_ = std::integral_constant<bool, B>;
template<class Head, class... Tail>
struct all_of
: bool_<Head::value && all_of<Tail...>::value>{};
template<class Head>
struct all_of<Head> : bool_<Head::value>{};
template<class C, class T = void>
using EnableIf = typename std::enable_if<C::value, T>::type;
// constructor
template<typename... U>
vector(U... args, EnableIf<all_of<std::is_scalar<U>...>>::type* = 0)
{
unpack_tuple(std::tie(args...)); // tie makes a tuple of references
}
The above code should work. However, as an advice, if you don't want something, static_assert that you don't get it and don't abuse SFINAE for that. :) SFINAE should only be used in overloaded contexts.
// constructor
template<typename... U>
vector(U... args)
{
static_assert(all_of<std::is_scalar<U>...>::value, "vector only accepts scalar types");
unpack_tuple(std::tie(args...)); // tie makes a tuple of references
}
So much for your actual question, but I recommend a better way to unpack tuples (or variadic arguments in general, or even an array), using the indices trick:
template<unsigned...> struct indices{};
template<unsigned N, unsigned... Is> struct indices_gen : indices_gen<N-1, N-1, Is...>{};
template<unsigned... Is> struct indices_gen<0, Is...> : indices<Is...>{};
template<unsigned... Is, class... U>
void unpack_args(indices<Is...>, U... args){
[](...){}((store[Is] = args, 0)...);
}
template<class... U>
vector(U... args){
static_assert(all_of<std::is_scalar<U>...>::value, "vector only accepts scalar types");
unpack_args(indices_gen<sizeof...(U)>(), args...);
}
What this code does is "abusing" the variadic unpacking mechanics. First, we generate a pack of indices [0 .. sizeof...(U)-1] and expand then this list in lockstep together with args. We put this expansion within a variadic (non-template) function argument list, as pack expansion can only occur at specific places, and this is one of them. Another possibility would be as a local array:
template<unsigned... Is, class... U>
void unpack_args(indices<Is...>, U... args){
int a[] = {(store[Is] = args, 0)...};
(void)a; // suppress unused variable warnings
}

Related

Subset a Variadic template given a constexpr boolean selection function

Suppose we have a variadic templated class like
template<class...Ts>
class X{
template<size_t I>
constexpr bool shouldSelect();
std::tuple<TransformedTs...> mResults; // this is want I want eventually
};
where the implementation of shouldSelect is not provided, but what it does is that, given an index i referring to the ith element of the variadic Ts, tells you whether we should select it to the subset.
I want to do a transformation on Ts such that only classes Ts at indexes that results in shouldSelect returning true should be selected. Is there an easy way to do this?
For example, if shouldSelect returns true for I = 1,2,4, and Ts... = short, int, double, T1, T2, then I want to get a TransformedTs... that is made up of int, double, T2. Then I can use this TransformedTs... in the same class.
If you're able to use C++17, this is pretty easy to implement using a combination of if constexpr and expression folding.
Start with some helper types, one with parameters to track the arguments to X::shouldSelect<I>(), and the other with a type to test.
template <typename T, size_t I, typename...Ts>
struct Accumulator {
using Type = std::tuple<Ts...>;
};
template <typename T>
struct Next { };
Then an operator overload either adds the type to the accumulator, or not with if constexpr:
template <typename TAcc, size_t I, typename... Ts, typename TArg>
decltype(auto) operator +(Accumulator<TAcc, I, Ts...>, Next<TArg>) {
if constexpr (TAcc::template shouldSelect<I>()) {
return Accumulator<TAcc, I + 1, Ts..., TArg>{};
} else {
return Accumulator<TAcc, I + 1, Ts...>{};
}
}
Finally, you can put it all together with a fold expression and extract the type with decltype:
template <template <typename... Ts> class T, typename... Ts>
constexpr decltype(auto) FilterImpl(const T<Ts...>&) {
return (Accumulator<T<Ts...>, 0>{} + ... + Next<Ts>{});
}
template<typename T>
using FilterT = typename decltype(FilterImpl(std::declval<T>()))::Type;
Usage:
using Result = FilterT<X<int, double, bool, etc>>;
Demo: https://godbolt.org/z/9h89zG
If you don't have C++17 available to you, it's still possible. You can do the same sort of conditional type transfer using a recursive inheritance chain to iterate though each type in the parameter pack, and std::enable_if to do the conditional copy. Below is the same code, but working in C++11:
// Dummy type for copying parameter packs
template <typename... Ts>
struct Mule {};
/* Filter implementation */
template <typename T, typename Input, typename Output, size_t I, typename = void>
struct FilterImpl;
template <typename T, typename THead, typename... TTail, typename... OutputTs, size_t I>
struct FilterImpl<T, Mule<THead, TTail...>, Mule<OutputTs...>, I, typename std::enable_if<( T::template shouldSelect<I>() )>::type >
: FilterImpl<T, Mule<TTail...>, Mule<OutputTs..., THead>, (I + 1)>
{ };
template <typename T, typename THead, typename... TTail, typename... OutputTs, size_t I>
struct FilterImpl<T, Mule<THead, TTail...>, Mule<OutputTs...>, I, typename std::enable_if<( !T::template shouldSelect<I>() )>::type >
: FilterImpl<T, Mule<TTail...>, Mule<OutputTs...>, (I + 1)>
{ };
template <typename T, typename... OutputTs, size_t I>
struct FilterImpl<T, Mule<>, Mule<OutputTs...>, I>
{
using Type = std::tuple<OutputTs...>;
};
/* Helper types */
template <typename T>
struct Filter;
template <template <typename... Ts> class T, typename... Ts>
struct Filter<T<Ts...>> : FilterImpl<T<Ts...>, Mule<Ts...>, Mule<>, 0>
{ };
template <typename T>
using FilterT = typename Filter<T>::Type;
Demo: https://godbolt.org/z/esso4M

find a tuple in a tuple of tuples

I have a set of classes, like A, B, C and a tuple of tuples containing these classes, like this:
struct A {
std::string name{"a"};
};
struct B {
std::string name{"b"};
};
struct C {
std::string name{"c"};
};
// only first items A(), B(), C() do matter, other are arbitrary
auto t = std::make_tuple(
std::make_tuple(A(), 1, 2, 3),
std::make_tuple(B(), 4, 5, 6),
std::make_tuple(C(), 7, 8)
);
My target logic is to select a tuple from a container tuple by match of type of the first element. So, by example above, I want to get string 'b', when calling something like this:
std::string the_b = having_first_of_type<B, decltype(t)>::get().name;
I am trying to get a solution with templates:
// a template for getting first item from N-th tuple int Tuples
template <std::size_t N, typename... Tuples>
using first_of_nth = std::tuple_element<0, std::tuple_element<N, std::tuple<Tuples...>>>;
template <std::size_t N, class T, class... Tuples>
struct having_first_of_type;
template <std::size_t N, class T, class... Tuples>
struct having_first_of_type<N,
typename std::enable_if<std::is_same<T, typename first_of_nth<N, Tuples...>::type>::value, T>::type* = nullptr>
{
static auto& get(const std::tuple<Tuples...>& tuple) {
return std::get<N>(tuple);
}
};
template <std::size_t N, class T, class... Tuples>
struct having_first_of_type<N,
typename std::enable_if<!std::is_same<T, typename first_of_nth<N, Tuples...>::type>::value, T>::type* = nullptr> : having_first_of_type<N-1, T, Tuples...>;
template <std::size_t N, class T, class... Tuples>
struct having_first_of_type<0, T, Tuples...> {}
And I can't form the specializations in the right way. For the first one (std::is_same is true) compiler says: error: expected '>' for position of '= nullptr'. It looks like it does not accept default value for T*, but I am confused why..
What's the error? Or, perhaps there is a better way to get what I want?
UPDATE
below are 2 working solutions: from N. Shead and #n314159 - thank you!
I forgot to mention that I tried to get it using C++14, but the solutions are for C++17.
C++17 is also OK.
Assigning a nullptr where a tyle belongs does not make sense. You should remove that. Further I am not exactly sure, what goes wrong. We can make the whole thing a bit easier by using the std::get version templated on a type not an index, then we don't have to carry the N:
#include <tuple>
#include <type_traits>
#include <iostream>
template<class T, class Tup, bool negated = false>
using first_of = std::enable_if_t<negated ^ std::is_same_v<std::tuple_element_t<0, Tup>, T>>;
template<class T, class= void, class... Tups>
struct first_match_impl;
template<class T, class Tup1, class... Tups>
struct first_match_impl<T, first_of<T, Tup1>, Tup1, Tups...> {
using type = Tup1;
template<class FullTup>
static Tup1& get(FullTup& t) {
return std::get<Tup1>(t);
}
};
template<class T, class Tup1, class... Tups>
struct first_match_impl<T, first_of<T, Tup1, true>, Tup1, Tups...>: first_match_impl<T, void, Tups...> {};
template<class T, class... Tups>
using first_match = first_match_impl<T, void, Tups...>;
template<class T, class... Tups>
auto& get_first_of(std::tuple<Tups...> &t) {
return first_match<T, Tups...>::get(t);
}
int main()
{
std::tuple<std::tuple<int, float>, std::tuple<char, double>> t {{1,2.}, {'A', 4.}};
std::cout << std::get<0>(get_first_of<char>(t)); // prints A
}
Note that this will not compile when you have two exactly identicall tuples in your tuple but will compile if there are different tuples with the same first element (then it will pick the first of them).
EDIT: This inspired me write a small library providing iterator like support for tuples. See here.
You've tried to give a default value in a place where the compiler expects a concrete type.
I'm assuming you want to get the whole inner tuple?
In that case, my attempt at solving this would look something like this:
template <typename T, typename Tuple>
constexpr bool tuple_first_type_is() {
if constexpr (std::tuple_size_v<Tuple> == 0)
return false;
else
return std::is_same_v<T, std::tuple_element_t<0, Tuple>>;
}
template <typename T, std::size_t I, typename NestedTuple>
constexpr decltype(auto) having_first_of_type_impl(NestedTuple&& nested_tuple) noexcept {
using D = std::decay_t<NestedTuple>;
static_assert(I < std::tuple_size_v<D>, "type not found in tuple");
using ith_tuple = std::tuple_element_t<I, D>;
if constexpr (tuple_first_type_is<T, ith_tuple>())
return std::get<I>(std::forward<NestedTuple>(nested_tuple));
else
return having_first_of_type_impl<T, I+1>(std::forward<NestedTuple>(nested_tuple));
}
template <typename T, typename NestedTuple>
constexpr decltype(auto) having_first_of_type(NestedTuple&& nested_tuple) noexcept {
static_assert(std::tuple_size_v<std::decay_t<NestedTuple>> > 0, "empty tuple");
return having_first_of_type_impl<T, 0>(std::forward<NestedTuple>(nested_tuple));
}
Live: http://coliru.stacked-crooked.com/a/aa1637939a5d7d7c
I'm not 100% confident I've done everything correctly with value categories and the like, and there could well be a better way of going about this, but this is the sort of thing I would start off with.

How to check the type of passed arguments to variadic function

I'm new to variadic templates and for the sake of learning consider the following function
template <typename T, typename... args>
T* make_arr(args... arg) {
// Code to check if passed args are of the same type
T* arr = new T[sizeof...(arg)]{ arg... };
return arr;
}
I have two questions:
I want the function to be templated and I want the passed arguments to be of the same type so the question: is it possible to check if passed arguments are of the same type ?
Is it possible to deduce the type of the array pointer by deducing the type of args..., I mean without using <typename T>? ... I used decltype(arg) but it didnt work ...
Note: please edit the title question if it is not appropriate ... thanks
The only way I found is to make a helper function using SFINAE
//Basic function
template<typename T>
void allsame(T) {}
//Recursive function
template<typename T, typename T2, typename... Ts,
typename = std::enable_if_t<std::is_same<T, T2>::value>>
void allsame(T arg, T2 arg2, Ts... args)
{
allsame(arg2, args...);
}
You can then call it like so:
allsame(arg...);
The compiler will then throw an error if the types are not the same.
For 2), you could modfiy allsame to return the type. The only drawback to this function is that it won't work if the type isn't default constructable.
template<typename T>
T allsame(T) { return{}; }
T allsame(T arg, T2 arg2, Ts... args)
Then, you can decltype(allsame(args...)) to get the type
First of all, you'll need these includes:
#include <type_traits>
#include <tuple>
then, let us declare variadic template to detect whether types are same or not:
template <typename ... args>
struct all_same : public std::false_type {};
template <typename T>
struct all_same<T> : public std::true_type {};
template <typename T, typename ... args>
struct all_same<T, T, args...> : public all_same<T, args ... > {};
now we can use static_assert to detect if parameters types are same:
template <typename T, typename... args>
T* make_arr(args... arg) {
// Code to check if passed args are of the same type
static_assert(all_same<args ...>::value, "Params must have same types");
T* arr = new T[sizeof...(arg)]{ arg... };
return arr;
};
Finally, let us take return type of your function as first type of parameters - if all types are same we can take any of them. We use std::tuple for this
template <typename... args>
typename std::tuple_element<0, std::tuple<args...> >::type * make_arr(args... arg) {
// Code to check if passed args are of the same type
static_assert(all_same<args ...>::value, "Params must have same types");
typedef typename std::tuple_element<0, std::tuple<args...> >::type T;
T* arr = new T[sizeof...(arg)]{ arg... };
return arr;
};
Start with a constexpr bool function to check if all the booleans are true. This will be useful when checking that all the is_same calls are true.
constexpr bool all() {
return true;
}
template<typename ...B>
constexpr bool all(bool b, B... bs) {
return b && all(bs...);
}
Anyway, here is the make_arr function:
template <typename... args
, class ...
, typename T = std::tuple_element_t<0, std::tuple<args...>>
, typename = std::enable_if_t< all(std::is_same<T, args>{}...) >
>
T* make_arr(args&&... arg) {
static_assert( all(std::is_same<T, args>{}...) ,"");
T* arr = new T[sizeof...(arg)]{ std::forward<args>(arg)... };
return arr;
}
A few comments:
perfect-forwarding is used, && and std::forward, to avoid potential copies if your type is large.
the type of the first argument is extracted by creating a std::tuple type and using std::tuple_element<0, ..>.
a static_assert is used, where each type is compared to the first type (via is_same).
I guess you want SFINAE to 'hide' this function unless the types are all identical. This is achieved via typename = std::enable_if_t< ..boolean-expression.. >
the class ... is essentially redundant. It's only purpose is to make it impossible for developers to cheat the checks by manually specifying the types (make_arr<int,char,size_t,bool>(..)). However, maybe this is too conservative - the static_assert will catch them anyway!

How can I arbitrarily sort a tuple's types?

One thing that really annoys me about C++ is that an empty struct/class takes up space.
So, I have this idea that std::tuple (or some variant, since it's (and the compiler's) implementation is highly implementation dependent) might be able to save the day, which it sort of does, but there are issues due to packing and alignment. Because of how compilers will align the items in the struct, having a empty next to a non-empty next to an empty next to a non-empty will be larger than 2 empties next to 2 non-empties.
Because of this, I need a way to reorder the types based on some criteria. Sorting the entire list based on size isn't necessary (and may in some cases be detrimental) so I need some generic way to reorder the tuple's type list but still access it as if the type list was in the original order.
I looked around a bit and haven't found anything like this and I'm at a loss. Ideas on how to accomplish this?
Example
struct A{};
struct B{};
// Need to be reordered based on some criteria.
std::tuple<A, int, B, float> x;
// In this case move all of the empty objects together like:
// std::tuple<A, B, int, float> x;
// but still have get<1>(x) return the `int` and get<2>(x) return `B`.
static_assert(std::is_same<decltype(get<0>()), A>::value, "0 should be type A");
static_assert(std::is_same<decltype(get<1>()), int>::value, "1 should be type int");
static_assert(std::is_same<decltype(get<2>()), B>::value, "2 should be type float");
static_assert(std::is_same<decltype(get<3>()), float>::value, "3 should be type B");
The reason this cannot be done by hand is that this could be part of a template and the elements in tuple may be empty or not, based on the parameters:
template <typename A, typename B, typename C, typename D>
class X
{
// Need to have this auto arranged given some criteria
// like size or move all of the empties together.
tuple<A, B, C, D> x;
public:
template<int i>
auto get() -> typename std::tuple_element<i, decltype(x)>
{
return get<i>(x);
}
};
// What are these types? Who knows. This could be buried in some
// template library somewhere.
X<T1, T2, T3, T4> x;
Building on what Barry did.
So from here I'd need a mapping meta-function to use the original
indices, how would I do that?
First, some helpers to facilitate index mapping. And because I'm lazy, I modified typelist slightly.
template <typename... Args>
struct typelist {
static constexpr std::size_t size = sizeof...(Args);
};
template<class T, std::size_t OldIndex, std::size_t NewIndex>
struct index_map_leaf {
using type = T;
static constexpr std::size_t old_index = OldIndex;
static constexpr std::size_t new_index = NewIndex;
};
template<class... Leaves>
struct index_map : Leaves... {};
Given a properly built index_map, converting from old index to new index is simple, leveraging template argument deduction and overload resolution:
template<std::size_t OldIndex, std::size_t NewIndex, class T>
index_map_leaf<T, OldIndex, NewIndex>
do_convert_index(index_map_leaf<T, OldIndex, NewIndex>);
template<std::size_t OldIndex, class IndexMap>
using converted_index_t = decltype(do_convert_index<OldIndex>(IndexMap()));
converted_index_t<OldIndex, IndexMap>::new_index is, well, the new index.
To build the index map, we do it in in three steps. We start by transforming the types into type-index pairs.
template<class... Ts, std::size_t... Is>
typelist<index_map_leaf<Ts, Is, 0>...>
do_build_old_indices(typelist<Ts...>, std::index_sequence<Is...>);
template<class TL>
using build_old_indices =
decltype(do_build_old_indices(TL(), std::make_index_sequence<TL::size>()));
Next, we partition the pairs. We need a metafunction that applies another metafunction to its arguments' nested typedef type rather than the arguments themselves.
// Given a metafunction, returns a metafunction that applies the metafunction to
// its arguments' nested typedef type.
template<class F>
struct project_type {
template<class... Args>
using apply = typename F::template apply<typename Args::type...>;
};
Given this, partitioning a typelist of index_map_leafs is simply partition_t<LeafList, project_type<F>>.
Finally, we transform the partitioned list, adding the new indices.
template<class... Ts, std::size_t... Is, std::size_t...Js>
typelist<index_map_leaf<Ts, Is, Js>...>
do_build_new_indices(typelist<index_map_leaf<Ts, Is, 0>...>,
std::index_sequence<Js...>);
template<class TL>
using build_new_indices =
decltype(do_build_new_indices(TL(), std::make_index_sequence<TL::size>()));
Bringing it all together,
template<class TL, class F>
using make_index_map =
apply_t<quote<index_map>, build_new_indices<partition_t<build_old_indices<TL>,
project_type<F>>>>;
With a little utility to convert the arguments of an arbitrary template to a type list:
template<template<class...> class T, class... Args>
typelist<Args...> do_as_typelist(typelist<T<Args...>>);
template<class T>
using as_typelist = decltype(do_as_typelist(typelist<T>()));
We can do the partitioning only once, by constructing the reordered tuple type directly from the index_map.
template<class Tuple, class F>
struct tuple_partitioner {
using map_type = make_index_map<as_typelist<Tuple>, F>;
using reordered_tuple_type = apply_t<project_type<quote<std::tuple>>,
as_typelist<map_type>>;
template<std::size_t OldIndex>
using new_index_for =
std::integral_constant<std::size_t,
converted_index_t<OldIndex, map_type>::new_index>;
};
For example, given
using original_tuple = std::tuple<int, double, long, float, short>;
using f = quote<std::is_integral>;
using partitioner = tuple_partitioner<original_tuple, f>;
The following assertions hold:
static_assert(partitioner::new_index_for<0>() == 0, "!");
static_assert(partitioner::new_index_for<1>() == 3, "!");
static_assert(partitioner::new_index_for<2>() == 1, "!");
static_assert(partitioner::new_index_for<3>() == 4, "!");
static_assert(partitioner::new_index_for<4>() == 2, "!");
static_assert(std::is_same<partitioner::reordered_tuple_type,
std::tuple<int, long, short, double, float>>{}, "!");
Demo.
P.S. Here's my version of filter:
template<typename A, typename F>
using filter_one = std::conditional_t<F::template apply<A>::value,
typelist<A>, typelist<>>;
template<typename F, typename... Args>
concat_t<filter_one<Args, F>...> do_filter(typelist<Args...>);
template <typename TL, typename F>
using filter_t = decltype(do_filter<F>(TL()));
First, let's start with the basics. We need a way to turn a template template (std::tuple) into a metafunction class:
template <template <typename...> class Cls>
struct quote {
template <typename... Args>
using apply = Cls<Args...>;
};
And a typelist:
template <typename... Args>
struct typelist { };
And something to go between them:
template <typename F, typename TL>
struct apply;
template <typename F, typename... Args>
struct apply<F, typelist<Args...>> {
using type = typename F::template apply<Args...>;
};
template <typename F, typename TL>
using apply_t = typename apply<F, TL>::type;
So that given some typelist, we can just have:
using my_tuple = apply_t<quote<std::tuple>, some_typelist>;
Now, we just need a partitioner on some criteria:
template <typename TL, typename F>
struct partition {
using type = concat_t<filter_t<TL, F>,
filter_t<TL, not_t<F>>
>;
};
Where concat:
template <typename... Args>
struct concat;
template <typename... Args>
using concat_t = typename concat<Args...>::type;
template <typename... A1, typename... A2, typename... Args>
struct concat<typelist<A1...>, typelist<A2...>, Args...> {
using type = concat_t<typelist<A1..., A2...>, Args...>;
};
template <typename TL>
struct concat<TL> {
using type = TL;
};
filter:
template <typename TL, typename F>
struct filter;
template <typename TL, typename F>
using filter_t = typename filter<TL, F>::type;
template <typename F>
struct filter<typelist<>, F> {
using type = typelist<>;
};
template <typename A, typename... Args, typename F>
struct filter<typelist<A, Args...>, F> {
using type = concat_t<
std::conditional_t<F::template apply<A>::value,
typelist<A>,
typelist<>>,
filter_t<typelist<Args...>, F>
>;
};
And not_:
template <typename F>
struct not_ {
template <typename Arg>
using apply = std::conditional_t<F::template apply<Args>::value,
std::false_type,
std::true_type>;
};
Which, given some_typelist of types that you want to put in your tuple becomes:
using my_tuple = apply_t<
quote<std::tuple>,
partition_t<
some_typelist,
some_criteria_metafunc_class
>>;

"unpack" std::array<T,N> as arguments to function

Here is quite nice (not mine) example how u can expand (or "explode") tuple as arguments to function:
template<int ...I> struct index_tuple_type {
template<int N> using append = index_tuple_type<I..., N>;
};
template<int N> struct make_index_impl {
using type = typename make_index_impl<N-1>::type::template append<N-1>;
};
template<> struct make_index_impl<0> { using type = index_tuple_type<>; };
template<int N> using index_tuple = typename make_index_impl<N>::type;
template <typename I, typename ...Args>
struct func_traits;
template <typename R, int ...I, typename ...Args>
struct func_traits<R, index_tuple_type<I...>, Args...> {
template <typename TT, typename FT>
static inline R call(TT &&t, FT &&f) {
return f(std::get<I>(std::forward<TT>(t))...);
}
};
template<
typename FT,
typename ...Args,
typename R = typename std::result_of<FT(Args&&...)>::type
>
inline R explode(std::tuple<Args...>& t, FT &&f) {
return func_traits<R, index_tuple<sizeof...(Args)>, Args...>
::call(t, std::forward<FT>(f));
}
then you can use this like so:
void test1(int i, char c) {
printf("%d %c\n", i, c);
}
int main() {
std::tuple<int, char> t1{57, 'a'};
explode(t1, test1);
}
Live version
I was wandering how could you do the same thing with std::array since it quite like tuple. std::get<N> works with std::array so I thought that it would be easy to modify this solution. But something like this doesn't work:
template<
typename FT,
typename Arg,
std::size_t I,
typename R = typename std::result_of<FT(Arg&&)>::type
>
inline R explode(std::array<Arg, I>& t, FT &&f) {
return func_traits<R, index_tuple<I>, Arg>::
call(t, std::forward<FT>(f));
}
void test2(int i1, int i2) {
printf("%d %d\n", i1, i2);
}
int main() {
std::array<int, int> t1{1, 2};
explode(t2, test1);
}
because of the part std::result_of<FT(Arg&&)>::type. The argument type Arg&& is wrong and result_of has no field type. For tuple Args&&... expanded, but now it should be "repeated" I times. Is there a way to do this using result_of so the returned type can be deducted?
Also i was wondering, having the tools to "unpack" tuple and array would it be possible to "unpack" recursively (probably using enable_if) structure like tuple<array<int, 2>, tuple<array<double,3>, ... and so on? Some kind of a tree where tuple and array are branches, and other types are leaves?
// enable argument dependent lookup on `get` call:
namespace aux {
using std::get;
template<size_t N, class T>
auto adl_get( T&& )->decltype( get<N>(std::declval<T>()) );
}
using aux::adl_get;
template<class F, class TupleLike, size_t...Is>
auto explode( F&& f, TupleLike&& tup, std::index_sequence<Is...> )
-> std::result_of_t< F( decltype(adl_get<Is>(std::forward<TupleLike>(tup)))... ) >
{
using std::get; // ADL support
return std::forward<F>(f)( get<Is>(std::forward<TupleLike>(tup))... );
}
is the first step. std::index_sequence is C++14, but it is easy to implement in C++11.
The next steps are also easy.
First, a traits class that dictates what types are tuple-like. I would go ahead and just duck-type use them, but a number of functions and traits classes we are going to use are not SFINAE friendly:
template<class T>
struct tuple_like:std::false_type{};
template<class... Ts>
struct tuple_like<std::tuple<Ts...>>:std::true_type{};
template<class... Ts>
struct tuple_like<std::pair<Ts...>>:std::true_type{};
template<class T, size_t N>
struct tuple_like<std::array<T,N>>:std::true_type{};
Next, an overload of explode that only works on tuple_like types:
template<class F, class TupleLike,
class TupleType=std::decay_t<TupleLike>, // helper type
class=std::enable_if_t<tuple_like<TupleType>{}>> // SFINAE tuple_like test
auto explode( F&& f, TupleLike&& tup )
-> decltype(
explode(
std::declval<F>(),
std::declval<TupleLike>(),
std::make_index_sequence<std::tuple_size<TupleType>{}>{}
)
)
{
using indexes = std::make_index_sequence<std::tuple_size<TupleType>{}>;
return explode(
std::forward<F>(f),
std::forward<TupleLike>(tup),
indexes{}
);
}
If you lack constexpr support you need to change some {} to ::value .
The above does the trick for pairs, arrays or tuples. If you want to add support for other tuple-like types, simply add a specialization to tuple_like and ensure std::tuple_size is specialized properly for your type and get<N> is ADL-overloaded (in the type's enclosing namespace).
std::make_index_sequence is also C++14 but easy to write in C++11.
template<size_t...>
struct index_sequence{};
namespace details {
template<size_t count, size_t...Is>
struct mis_helper:mis_helper<count-1, count-1, Is...> {};
template<size_t...Is>
struct mis_helper<0,Is...> {
using type=index_sequence<Is...>;
};
}
template<size_t count>
using make_index_sequence=typename details::mis_helper<count>::type;
(this is poor QOI for a C++14 library, which should use at least log descent, as it requires O(n) template recursive template instantiations for a list of size n. However, is n is less than a few 100, it won't matter).
std::enable_if_t<?> is C++14, but in C++11 is just typename std::enable_if<?>::type.