Consider this
int foo (int a, char c, bool b) {std::cout << a << ' ' << c << ' ' << b << '\n'; return 8;}
double bar (int a, char c, bool b, int d) {std::cout << a << ' ' << c << ' ' << b << ' ' << d << '\n'; return 2.5;}
char baz (bool a, bool b) {std::cout << a << ' ' << b << '\n'; return 'a';}
int main() {
const auto tuple = std::make_tuple(5, true, 'a', 3.5, false, 1000, 't', 2, true, 5.8);
const std::tuple<int, double, char> t = searchArguments (tuple, foo, bar, baz);
}
So the arguments for foo are first searched (from tuple). Searching from left to right, the first int found is 5, the first char found is a, and the first bool found is true. So then foo(5,a,true) is called. Similarly for bar and baz. Except bar takes 2 ints, and we don't want it to take 5 twice, but rather 5 and then 1000. Similarly, baz is to take (true, false) for its arguments instead of (true, true).
My current solution below unfortunately outputs precisely what I just said should not be outputted:
foo(5,a,true) // OK
bar(5,a,true,5) // Nope, we want bar(5,a,true,1000)
baz(true,true) // Nope, we want baz(true,false)
I realize that one possible (ugly) way to fix my current solution:
#include <iostream>
#include <tuple>
#include <utility>
// C++17 std::apply
template <typename F, typename Tuple, size_t... Is>
auto apply_impl (F&& f, Tuple&& tuple, const std::index_sequence<Is...>&) {
return (std::forward<F>(f))(std::get<Is>(std::forward<Tuple>(tuple))...);
}
template <typename F, typename Tuple>
auto apply (F&& f, Tuple&& tuple) { // Invoke the Callable object f with a tuple of arguments.
return apply_impl(std::forward<F>(f), std::forward<Tuple>(tuple), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>());
}
// FunctionTraits
template <typename> struct FunctionTraits;
template <typename R, typename... Args>
struct FunctionTraits<R(Args...)> : std::integral_constant<std::size_t, sizeof...(Args)> {
using args_type = std::tuple<Args...>;
using return_type = R;
};
template <typename R, typename... Args>
struct FunctionTraits<R(*)(Args...)> : FunctionTraits<R(Args...)> {};
template <typename R, typename... Args>
struct FunctionTraits<R(&)(Args...)> : FunctionTraits<R(Args...)> {};
// etc... for other callable types.
namespace getFirstDetail {
template <typename T, typename Tuple, std::size_t N, bool>
struct SearchTuple : SearchTuple<T, Tuple, N+1, std::is_same<std::tuple_element_t<N+1, Tuple>, T>::value> {};
template <typename T, typename Tuple, std::size_t N>
struct SearchTuple<T, Tuple, N, true> {
static T search (const Tuple& tuple) {return std::get<N>(tuple);}
};
}
// Get the first element of a tuple whose type is T. Note that using std::get<T> will not suffice since this fails to compile if the tuple has more than one element of type T.
// It is the client's responsiblity to ensure that such an element in the tuple exists (else there will be a crash).
template <typename T, typename Tuple>
T getFirst (const Tuple& tuple) {
return getFirstDetail::SearchTuple<T, Tuple, -1, false>::search(tuple);
}
namespace searchArgumentsDetail {
template <typename> struct Search;
template <typename... Args>
struct Search<std::tuple<Args...>> {
template <typename R, typename Tuple, typename F>
static R execute (const Tuple& tuple, F f) {return apply(f, std::make_tuple(getFirst<Args>(tuple)...));}
};
}
template <typename Tuple>
std::tuple<> searchArguments (const Tuple&) {return std::tuple<>();}
// Gathers the first possible elements from 'tuple' that 'f' can accept (reading from left to right) and carries out the function. Then it is repeated for the remaining functions fs...
template <typename Tuple, typename F, typename... Fs>
auto searchArguments (const Tuple& tuple, F f, Fs... fs) {
using ArgsType = typename FunctionTraits<F>::args_type;
using ReturnType = typename FunctionTraits<F>::return_type;
const auto singleTuple = std::make_tuple (searchArgumentsDetail::Search<ArgsType>::template execute<ReturnType>(tuple, f));
return std::tuple_cat (singleTuple, searchArguments (tuple, fs...));
}
// Testing
int foo (int a, char c, bool b) {std::cout << a << ' ' << c << ' ' << b << '\n'; return 8;}
double bar (int a, char c, bool b, int d) {std::cout << a << ' ' << c << ' ' << b << ' ' << d << '\n'; return 2.5;}
char baz (bool a, bool b) {std::cout << a << ' ' << b << '\n'; return 'a';}
int main() {
const auto tuple = std::make_tuple(5, true, 'a', 3.5, false, 1000, 't', 2, true, 5.8);
std::cout << std::boolalpha;
const std::tuple<int, double, char> t = searchArguments (tuple, foo, bar, baz);
std::cout << std::get<0>(t) << ' ' << std::get<1>(t) << ' ' << std::get<2>(t) << '\n'; // 8 2.5 a
std::cin.get();
}
is to remove each element used from the tuple and pass the smaller tuple to the next recursion, thus guaranteeing that those repeat arguments don't occur. But that's a real mess (and probably unnecessarily inefficient). Furthermore, when calling up the next function, we need to restart with the original tuple again, and thus the original tuple must be passed around as well as each truncated tuple. I just want to ask if there is a better, much more elegant solution than this before I leap into this nightmarish task (if it even works at all).
Update: A new idea I thought of (if simply trying to fix my current solution), is to modify my getFirst function to getN<N...>, where N = 1 means get the first, N = 2 means get the second, etc...? But then there is the responsibility of updating the latest N value.
#include <utility>
#include <type_traits>
#include <tuple>
namespace detail {
template <std::size_t, int, typename, typename, typename=void>
constexpr std::size_t find = -1;
template <std::size_t I, int dir, typename U, typename Ts>
constexpr auto find<I, dir, U, Ts, std::enable_if_t<(I < std::tuple_size<Ts>{})>>
= std::is_same<std::tuple_element_t<I, Ts>, U>{}? I : find<I+dir, dir, U, Ts>;
template <typename, typename ISeq, std::size_t, typename>
struct obtain_indices {using type = ISeq;};
template <typename Ts, std::size_t... Is, std::size_t u, typename Us>
struct obtain_indices<Ts, std::integer_sequence<
std::enable_if_t<(u < std::tuple_size<Us>{}), std::size_t>, Is...>, u, Us> {
static constexpr std::array<std::size_t, sizeof...(Is)> indices = {Is...};
using C = std::tuple_element_t<u, Us>;
static constexpr auto previous = find<u-1, -1, C, Us>;
using type = typename obtain_indices<Ts, std::index_sequence<Is...,
find<previous != -1? indices[previous]+1 : 0, 1, C, Ts>>, u+1, Us>::type;
};
// General overload once indices have been determined
template <typename Tup, typename F, std::size_t... Is>
constexpr decltype(auto) invoke(F&& f, Tup&& t,
std::index_sequence<Is...>) {
return std::forward<F>(f)(std::get<Is>(std::forward<Tup>(t))...);
}
} // end namespace detail
// For function pointers
template <typename Tup, typename R, typename... Args>
constexpr decltype(auto) invoke(R(*f)(Args...), Tup&& t) {
return detail::invoke(f, std::forward<Tup>(t),
typename detail::obtain_indices<std::decay_t<Tup>,
std::index_sequence<>, 0, std::tuple<std::decay_t<Args>...>>::type{});
}
From your example:
#include <iostream>
double bar (int a, char c, bool b, int d) {
std::cout << a << ' ' << c << ' ' << b << ' ' << d << '\n';
return 2.5;
}
int main() {
const auto tuple = std::make_tuple(5, true, 'a', 3.5,
false, 1000, 't', 2, true, 5.8);
invoke(bar, tuple);
}
Demo.
Here is an alternative I've found to Columbo's method. Instead of searching backwards through the args tuple to determine if T was searched before already, store Pair<T,index+1> in a pack. Then the search begins at position 0 only if T is not found among the Pairs in that pack, else at the index+1 position. I don't know which method is more efficient though.
#include <iostream>
#include <utility>
#include <type_traits>
#include <tuple>
template <typename, std::size_t> struct Pair;
template <typename Tuple, typename F, std::size_t... Is>
constexpr decltype(auto) partial_apply (Tuple&& tuple, F&& f, std::index_sequence<Is...>) {
return std::forward<F>(f)(std::get<Is>(std::forward<Tuple>(tuple))...);
}
// FunctionTraits
template <typename> struct FunctionTraits;
template <typename R, typename... Args>
struct FunctionTraits<R(Args...)> : std::integral_constant<std::size_t, sizeof...(Args)> {
using args_type = std::tuple<std::decay_t<Args>...>;
using return_type = R;
};
template <typename R, typename... Args>
struct FunctionTraits<R(*)(Args...)> : FunctionTraits<R(Args...)> {};
template <typename R, typename... Args>
struct FunctionTraits<R(&)(Args...)> : FunctionTraits<R(Args...)> {};
// etc... for other callable types.
template <typename Tuple, typename T, std::size_t Start, typename = void>
struct Find : std::integral_constant<std::size_t, -1> {};
template <typename Tuple, typename T, std::size_t Start>
struct Find<Tuple, T, Start, std::enable_if_t<(Start < std::tuple_size<Tuple>::value)>> {
static constexpr size_t value = std::is_same<T, std::tuple_element_t<Start, Tuple>>::value ? Start : Find<Tuple, T, Start+1>::value;
};
template <typename T, typename... Pairs> struct SearchPairs;
template <typename T>
struct SearchPairs<T> : std::integral_constant<std::size_t, 0> {};
template <typename T, typename First, typename... Rest>
struct SearchPairs<T, First, Rest...> : SearchPairs<T, Rest...> {};
template <typename T, std::size_t I, typename... Rest>
struct SearchPairs<T, Pair<T,I>, Rest...> : std::integral_constant<std::size_t, I> {};
template <typename Tuple, typename ArgsTuple, std::size_t Start, typename Indices, typename LastIndices, typename = void>
struct ObtainIndices {
using type = Indices;
};
template <typename Tuple, typename ArgsTuple, std::size_t Start, std::size_t... Is, typename... Pairs>
struct ObtainIndices<Tuple, ArgsTuple, Start, std::index_sequence<Is...>, std::tuple<Pairs...>,
std::enable_if_t<(Start < std::tuple_size<ArgsTuple>::value)> > {
using T = std::tuple_element_t<Start, ArgsTuple>;
static constexpr std::size_t start = SearchPairs<T, Pairs...>::value, // Searching through Pairs..., and will be 0 only if T is not found among the pairs. Else we start after where the last T was found in Tuple.
index = Find<Tuple, T, start>::value;
using type = typename ObtainIndices<Tuple, ArgsTuple, Start+1,
std::index_sequence<Is..., index>, std::tuple<Pair<T, index+1>, Pairs...>>::type;
// 'index+1' because we start searching for T again (if ever) after position 'index'. Also, we must place Pair<T, index+1> before the Pairs... pack rather than after it because if a Pair with T already exists, that Pair must not be used again.
};
template <typename Tuple, typename F>
constexpr decltype(auto) searchArguments (Tuple&& t, F&& f) {
using IndexSequence = typename ObtainIndices<std::decay_t<Tuple>, typename FunctionTraits<std::decay_t<F>>::args_type, 0, std::index_sequence<>, std::tuple<>>::type;
return partial_apply(std::forward<Tuple>(t), std::forward<F>(f), IndexSequence{});
}
// Testing
int foo (int a, char c, bool b, int d, bool e, int f) {std::cout << "foo(" << a << ", " << c << ", " << b << ", " << d << ", " << e << ", " << f << ")\n"; return 8;}
int main() {
const auto tuple = std::make_tuple(3.14, "bye", 5, true, 'a', 3.5, 20, false, 1000, 't', true, 5.8);
std::cout << std::boolalpha;
const int a = searchArguments(tuple, foo); // foo(5, a, true, 20, false, 1000)
std::cout << a << '\n'; // 8
}
Related
What is the problem with this?
struct foo {
void process(int, char, bool) {}
};
foo myfoo;
template <typename Method> struct thing {
void doit() {
Method m = Method{};
(myfoo.*m)(5, 'a', true);
}
};
int main() {
thing<decltype(&foo::process)> t;
t.doit();
}
I think this isolates the problem. What is the workaround if I have to use the type Method, as in the case of my original post below?
Original post:
In the following attempted test:
struct Foo { int play (char, bool) {return 3;} };
struct Bar { double jump (int, short, float) {return 5.8;} };
struct Baz { char run (double) {return 'b';} };
int main() {
Foo foo; Bar bar; Baz baz;
Functor<decltype(&Foo::play), decltype(&Bar::jump), decltype(&Baz::run)> func;
func(foo, bar, baz, 'c', true, 5, 2, 4.5, 6.8);
}
As you can predict, func is supposed to carry out
foo.play('c', true); bar.jump(5, 2, 4.5); baz.run(6.8);
My implementation of the Functor class so far (ignoring perfect forwarding and such for now) is
template <typename... Members>
struct Functor {
using m = many_members<Members...>;
template <typename... Args>
typename m::return_types operator()(Args... args) const { // perfect forwarding to do later
auto t = std::make_tuple(args...);
auto objects = utilities::tuple_head<sizeof...(Members)>(t);
auto arguments = utilities::extract_subtuple<sizeof...(Members), sizeof...(Args) - sizeof...(Members)>(t);
call(objects, arguments); // Won't compile on GCC 7.2 or clang 6.0.
}
private:
template <typename Tuple1, typename Tuple2>
auto call (Tuple1& objects, const Tuple2& args) const {
std::invoke(typename utilities::nth_element<0, Members...>::type{}, std::get<0>(objects), 'c', true);
}
};
where my last line using std::invoke is just to test the concept before I continue. It however will not compile on either GCC 7.2 or clang 6.0, so I cannot continue with the generalization. Any workaround here, or a completely different implementation altogether?
Here is everything I have so far:
namespace utilities {
template <std::size_t N, typename... Ts>
struct nth_element : std::tuple_element<N, std::tuple<Ts...>> { };
template <std::size_t Skip, std::size_t Take, typename Tuple>
auto extract_subtuple (const Tuple&, std::enable_if_t<(Take == 0)>* = nullptr) {
return std::tuple<>();
}
template <std::size_t Skip, std::size_t Take, typename Tuple>
auto extract_subtuple (const Tuple& tuple, std::enable_if_t<(Take > 0)>* = nullptr) {
return std::tuple_cat (std::make_tuple(std::get<Skip>(tuple)), extract_subtuple<Skip + 1, Take - 1>(tuple));
}
template <std::size_t N, typename Tuple>
auto tuple_head (const Tuple& tuple) {
return extract_subtuple<0, N>(tuple);
}
}
template <typename Rs, typename Ts, typename ArgsPacks, typename AllArgs, typename... Members> struct many_members_h;
template <typename Rs, typename Ts, typename ArgsPacks, typename AllArgs>
struct many_members_h<Rs, Ts, ArgsPacks, AllArgs> {
using return_types = Rs;
using classes = Ts;
using args_packs = ArgsPacks;
using all_args = AllArgs;
};
template <typename... Rs, typename... Ts, typename... ArgsPacks, typename... AllArgs, typename R, typename T, typename... Args, typename... Rest>
struct many_members_h<std::tuple<Rs...>, std::tuple<Ts...>, std::tuple<ArgsPacks...>, std::tuple<AllArgs...>, R(T::*)(Args...), Rest...> :
many_members_h<std::tuple<Rs..., R>, std::tuple<Ts..., T>, std::tuple<ArgsPacks..., std::tuple<Args...>>, std::tuple<AllArgs..., Args...>, Rest...> { };
template <typename... Members>
struct many_members : many_members_h<std::tuple<>, std::tuple<>, std::tuple<>, std::tuple<>, Members...> { };
template <typename... Members>
struct Functor {
using m = many_members<Members...>;
template <typename... Args>
typename m::return_types operator()(Args... args) const { // perfect forwarding to do later
auto t = std::make_tuple(args...);
auto objects = utilities::tuple_head<sizeof...(Members)>(t);
auto arguments = utilities::extract_subtuple<sizeof...(Members), sizeof...(Args) - sizeof...(Members)>(t);
call(objects, arguments); // Won't compile on GCC 7.2 or clang 6.0.
}
private:
template <typename Tuple1, typename Tuple2>
auto call (Tuple1& objects, const Tuple2& args) const {
std::invoke(typename utilities::nth_element<0, Members...>::type{}, std::get<0>(objects), 'c', true);
}
};
// Testing
#include <iostream>
struct Foo { int play (char, bool) {return 3;} };
struct Bar { double jump (int, short, float) {return 5.8;} };
struct Baz { char run (double) {return 'b';} };
int main() {
Foo foo; Bar bar; Baz baz;
Functor<decltype(&Foo::play), decltype(&Bar::jump), decltype(&Baz::run)> func;
func(foo, bar, baz, 'c', true, 5, 2, 4.5, 6.8);
}
Taking your smaller first example, note that decltype(&foo::process) is the type called void (foo::*)(int, char, bool).
This type does not contain or imply any association with the original function foo::process itself. Just like the type int doesn't let you get the value of some particular int elsewhere in your program, or the type SomeClass doesn't let you refer to a SomeClass object elsewhere in your program, the type alone doesn't carry a value or identity.
The expression Method{} value-initializes this pointer to member type. Which means the resulting value is a null pointer value. Which means calling it is undefined behavior (and on many systems is likely to result in a segfault).
If you're using C++17 mode, you could use a template <auto Method> non-type parameter and simply pass &foo::process (without using decltype) as the template argument. Some SFINAE techniques could enforce that the argument is actually a pointer to member function, and some helper traits could be used to get the class type and parameter list tuple.
Or if you're using a standard earlier than C++17, you'll have to either make the function pointer a function argument, or make it a template parameter which follows the type, as in template <typename MethodType, MethodType Method>, then call as thing<decltype(&foo::process), &foo::process>.
Thanks to aschepler's answer and advice to use auto... instead of typename... for the member function pointers, I was able to carry the original goal:
#include <tuple>
#include <functional> // std::invoke
#include <type_traits>
#include <utility>
namespace utilities {
template <std::size_t N, auto I, auto... Is>
struct nth_element : nth_element<N - 1, Is...> { };
template <auto I, auto... Is>
struct nth_element<0, I, Is...> {
static constexpr decltype(I) value = I;
};
template <std::size_t N, typename Pack> struct nth_index;
template <std::size_t N, std::size_t... Is>
struct nth_index<N, std::index_sequence<Is...>> : nth_element<N, Is...> { };
template <std::size_t Skip, std::size_t Take, typename Tuple>
auto extract_subtuple (const Tuple&, std::enable_if_t<(Take == 0)>* = nullptr) {
return std::tuple<>();
}
template <std::size_t Skip, std::size_t Take, typename Tuple>
auto extract_subtuple (const Tuple& tuple, std::enable_if_t<(Take > 0)>* = nullptr) {
return std::tuple_cat (std::make_tuple(std::get<Skip>(tuple)), extract_subtuple<Skip + 1, Take - 1>(tuple));
}
template <std::size_t N, typename Tuple>
auto tuple_head (const Tuple& tuple) {
return extract_subtuple<0, N>(tuple);
}
template <typename F, typename T, typename Tuple, std::size_t... Is>
decltype(auto) invoke_with_tuple_h (F&& f, T&& t, Tuple&& tuple, std::index_sequence<Is...>&&) {
return std::invoke(std::forward<F>(f), std::forward<T>(t), std::get<Is>(std::forward<Tuple>(tuple))...);
}
template <typename F, typename T, typename Tuple>
decltype(auto) invoke_with_tuple (F&& f, T&& t, Tuple&& tuple) {
return invoke_with_tuple_h (std::forward<F>(f), std::forward<T>(t), std::forward<Tuple>(tuple), std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>{});
}
template <typename PartialSums, std::size_t Sum, std::size_t... Is> struct all_partial_sums_h;
template <std::size_t... PartialSums, std::size_t Sum>
struct all_partial_sums_h<std::index_sequence<PartialSums...>, Sum> {
using type = std::index_sequence<PartialSums..., Sum>;
using type_without_last_sum = std::index_sequence<PartialSums...>; // We define this because this is what we need actually.
};
template <std::size_t... PartialSums, std::size_t Sum, std::size_t First, std::size_t... Rest>
struct all_partial_sums_h<std::index_sequence<PartialSums...>, Sum, First, Rest...> :
all_partial_sums_h<std::index_sequence<PartialSums..., Sum>, Sum + First, Rest...> { };
template <typename Pack> struct all_partial_sums;
template <std::size_t... Is>
struct all_partial_sums<std::index_sequence<Is...>> : all_partial_sums_h<std::index_sequence<>, 0, Is...> { };
template <typename Pack> struct pack_size;
template <template <typename...> class P, typename... Ts>
struct pack_size<P<Ts...>> : std::integral_constant<std::size_t, sizeof...(Ts)> { };
template <typename PackOfPacks> struct get_pack_sizes;
template <template <typename...> class P, typename... Packs>
struct get_pack_sizes<P<Packs...>> {
using type = std::index_sequence<pack_size<Packs>::value...>;
};
}
template <typename Method> struct method_traits;
template <typename R, typename C, typename... Args>
struct method_traits<R(C::*)(Args...)> {
using return_type = R;
using class_type = C;
using args_type = std::tuple<Args...>;
};
template <typename Rs, typename Cs, typename ArgsPacks, auto... Members> struct many_members_h;
template <typename Rs, typename Cs, typename ArgsPacks>
struct many_members_h<Rs, Cs, ArgsPacks> {
using return_types = Rs;
using classes = Cs;
using args_packs = ArgsPacks;
};
template <typename... Rs, typename... Cs, typename... ArgsPacks, auto F, auto... Rest>
struct many_members_h<std::tuple<Rs...>, std::tuple<Cs...>, std::tuple<ArgsPacks...>, F, Rest...> :
many_members_h<std::tuple<Rs..., typename method_traits<decltype(F)>::return_type>, std::tuple<Cs..., typename method_traits<decltype(F)>::class_type>, std::tuple<ArgsPacks..., typename method_traits<decltype(F)>::args_type>, Rest...> { };
template <auto... Members>
struct many_members : many_members_h<std::tuple<>, std::tuple<>, std::tuple<>, Members...> { };
template <auto... Members>
struct Functor {
using m = many_members<Members...>;
using starting_points = typename utilities::all_partial_sums<typename utilities::get_pack_sizes<typename m::args_packs>::type>::type;
template <typename... Args>
typename m::return_types operator()(Args&&... args) const {
constexpr std::size_t M = sizeof...(Members);
auto t = std::make_tuple(std::forward<Args>(args)...);
auto objects = utilities::tuple_head<M>(t);
auto arguments = utilities::extract_subtuple<M, sizeof...(Args) - M>(t);
return call(objects, arguments, std::make_index_sequence<M>{});
}
private:
template <typename Tuple1, typename Tuple2, std::size_t... Is>
auto call (Tuple1& objects, const Tuple2& args, std::index_sequence<Is...>&&) const { // perfect forwarding to do later
return std::make_tuple(call_helper<Is>(objects, args)...);
}
template <std::size_t N, typename Tuple1, typename Tuple2>
auto call_helper (Tuple1& objects, const Tuple2& args) const { // perfect forwarding to do later
constexpr std::size_t s = std::tuple_size_v<std::tuple_element_t<N, typename m::args_packs>>;;
constexpr std::size_t a = utilities::nth_index<N, starting_points>::value;
const auto args_tuple = utilities::extract_subtuple<a, s>(args);
return utilities::invoke_with_tuple (utilities::nth_element<N, Members...>::value, std::get<N>(objects), args_tuple);
}
};
// Testing
#include <iostream>
struct Foo { int play (char c, bool b) { std::cout << std::boolalpha << "Foo::play(" << c << ", " << b << ") called.\n"; return 3; } };
struct Bar { double jump (int a, short b, float c) { std::cout << "Bar::jump(" << a << ", " << b << ", " << c << ") called.\n"; return 5.8; } };
struct Baz { char run (double d) { std::cout << "Baz::run(" << d << ") called.\n"; return 'b'; } };
int main() {
Foo foo; Bar bar; Baz baz;
Functor<&Foo::play, &Bar::jump, &Baz::run> func;
const auto tuple = func(foo, bar, baz, 'c', true, 5, 2, 4.5, 6.8);
std::cin.get();
}
Output:
Baz::run(6.8) called.
Bar::jump(5, 2, 4.5) called.
Foo::play(c, true) called.
Consider this output:
int foo (int, char) {std::cout << "foo\n"; return 0;}
double bar (bool, double, long ) {std::cout << "bar\n"; return 3.5;}
bool baz (char, short, float) {std::cout << "baz\n"; return true;}
int main() {
const auto tuple = std::make_tuple(5, 'a', true, 3.5, 1000, 't', 2, 5.8);
multiFunction<2,3,3> (tuple, foo, bar, baz); // foo bar baz
}
So multiFunction<2,3,3> takes the first 2 elements of tuple and passes them to foo, the next 3 elements of tuple and passes them to bar, etc... I got this working (except when the functions have overloads, which is a separate problem). But the return values of each function called are lost. I want those return values stored somewhere, something like
std::tuple<int, double, bool> result = multiFunction<2,3,3> (tuple, foo, bar, baz);
But I don't know how to implement that. For those who want to help get this done, here is my (updated) working code so far, which stores the outputs into a stringstream only. Not easy to get all the values back, especially if the objects saved in the stream are complex classes.
#include <iostream>
#include <tuple>
#include <utility>
#include <sstream>
template <std::size_t N, typename Tuple>
struct TupleHead {
static auto get (const Tuple& tuple) { // The subtuple from the first N components of tuple.
return std::tuple_cat (TupleHead<N-1, Tuple>::get(tuple), std::make_tuple(std::get<N-1>(tuple)));
}
};
template <typename Tuple>
struct TupleHead<0, Tuple> {
static auto get (const Tuple&) { return std::tuple<>{}; }
};
template <std::size_t N, typename Tuple>
struct TupleTail {
static auto get (const Tuple& tuple) { // The subtuple from the last N components of tuple.
return std::tuple_cat (std::make_tuple(std::get<std::tuple_size<Tuple>::value - N>(tuple)), TupleTail<N-1, Tuple>::get(tuple));
}
};
template <typename Tuple>
struct TupleTail<0, Tuple> {
static auto get (const Tuple&) { return std::tuple<>{}; }
};
template <typename Tuple, typename F, std::size_t... Is>
auto functionOnTupleHelper (const Tuple& tuple, F f, const std::index_sequence<Is...>&) {
return f(std::get<Is>(tuple)...);
}
template <typename Tuple, typename F>
auto functionOnTuple (const Tuple& tuple, F f) {
return functionOnTupleHelper (tuple, f, std::make_index_sequence<std::tuple_size<Tuple>::value>{});
}
template <typename Tuple, typename... Functions> struct MultiFunction;
template <typename Tuple, typename F, typename... Fs>
struct MultiFunction<Tuple, F, Fs...> {
template <std::size_t I, std::size_t... Is>
static inline auto execute (const Tuple& tuple, std::ostringstream& oss, const std::index_sequence<I, Is...>&, F f, Fs... fs) {
const auto headTuple = TupleHead<I, Tuple>::get(tuple);
const auto tailTuple = TupleTail<std::tuple_size<Tuple>::value - I, Tuple>::get(tuple);
// functionOnTuple (headTuple, f); // Always works, though return type is lost.
oss << std::boolalpha << functionOnTuple (headTuple, f) << '\n'; // What about return types that are void???
return MultiFunction<std::remove_const_t<decltype(tailTuple)>, Fs...>::execute (tailTuple, oss, std::index_sequence<Is...>{}, fs...);
}
};
template <>
struct MultiFunction<std::tuple<>> {
static auto execute (const std::tuple<>&, std::ostringstream& oss, std::index_sequence<>) { // End of recursion.
std::cout << std::boolalpha << oss.str();
// Convert 'oss' into the desired tuple? But how?
return std::tuple<int, double, bool>(); // This line is just to make the test compile.
}
};
template <std::size_t... Is, typename Tuple, typename... Fs>
auto multiFunction (const Tuple& tuple, Fs... fs) {
std::ostringstream oss;
return MultiFunction<Tuple, Fs...>::execute (tuple, oss, std::index_sequence<Is...>{}, fs...);
}
// Testing
template <typename T> int foo (int, char) {std::cout << "foo<T>\n"; return 0;}
double bar (bool, double, long ) {std::cout << "bar\n"; return 3.5;}
template <int...> bool baz (char, short, float) {std::cout << "baz<int...>\n"; return true;}
int main() {
const auto tuple = std::make_tuple(5, 'a', true, 3.5, 1000, 't', 2, 5.8);
std::tuple<int, double, bool> result = multiFunction<2,3,3> (tuple, foo<bool>, bar, baz<2,5,1>); // foo<T> bar baz<int...>
}
Here's an approach where the number of arguments is deduced greedily:
#include <tuple>
namespace detail {
using namespace std;
template <size_t, size_t... Is, typename Arg>
constexpr auto call(index_sequence<Is...>, Arg&&) {return tuple<>{};}
template <size_t offset, size_t... Is, typename ArgT, typename... Fs>
constexpr auto call(index_sequence<Is...>, ArgT&&, Fs&&...);
template <size_t offset, size_t... Is,
typename ArgT, typename F, typename... Fs,
typename=decltype(declval<F>()(get<offset+Is>(declval<ArgT>())...))>
constexpr auto call(index_sequence<Is...>, ArgT&& argt, F&& f, Fs&&... fs) {
return tuple_cat(make_tuple(f(get<offset+I>(forward<ArgT>(argt))...)),
call<offset+sizeof...(Is)>(index_sequence<>{},
forward<ArgT>(argt),
forward<Fs>(fs)...));}
template <size_t offset, size_t... Is, typename ArgT, typename... Fs>
constexpr auto call(index_sequence<Is...>, ArgT&& argt, Fs&&... fs) {
return call<offset>(index_sequence<Is..., sizeof...(Is)>{},
forward<ArgT>(argt), forward<Fs>(fs)...);}
}
template <typename ArgT, typename... Fs>
constexpr auto multifunction(ArgT&& argt, Fs&&... fs) {
return detail::call<0>(std::index_sequence<>{},
std::forward<ArgT>(argt), std::forward<Fs>(fs)...);}
Demo. However, the above has quadratic time complexity in the number of return values, because tuple_cat is called recursively. Instead, we can use a slightly modified version of call to obtain the indices for each call - the actual tuple is then obtained directly:
#include <tuple>
namespace detail {
using namespace std;
template <size_t, size_t... Is, typename Arg>
constexpr auto indices(index_sequence<Is...>, Arg&&) {return tuple<>{};}
template <size_t offset, size_t... Is, typename ArgT, typename... Fs>
constexpr auto indices(index_sequence<Is...>, ArgT&&, Fs&&...);
template <size_t offset, size_t... Is, typename ArgT, typename F, class... Fs,
typename=decltype(declval<F>()(get<offset+Is>(declval<ArgT>())...))>
constexpr auto indices(index_sequence<Is...>, ArgT&& argt, F&& f, Fs&&... fs){
return tuple_cat(make_tuple(index_sequence<offset+Is...>{}),
indices<offset+sizeof...(Is)>(index_sequence<>{},
forward<ArgT>(argt),
forward<Fs>(fs)...));}
template <size_t offset, size_t... Is, typename ArgT, typename... Fs>
constexpr auto indices(index_sequence<Is...>, ArgT&& argt, Fs&&... fs) {
return indices<offset>(index_sequence<Is..., sizeof...(Is)>{},
forward<ArgT>(argt), forward<Fs>(fs)...);}
template <typename Arg, typename F, size_t... Is>
constexpr auto apply(Arg&& a, F&& f, index_sequence<Is...>) {
return f(get<Is>(a)...);}
template <typename ITuple, typename Args, size_t... Is, typename... Fs>
constexpr auto apply_all(Args&& args, index_sequence<Is...>, Fs&&... fs) {
return make_tuple(apply(forward<Args>(args), forward<Fs>(fs),
tuple_element_t<Is, ITuple>{})...);
}
}
template <typename ArgT, typename... Fs>
constexpr auto multifunction(ArgT&& argt, Fs&&... fs) {
return detail::apply_all<decltype(detail::indices<0>(std::index_sequence<>{},
std::forward<ArgT>(argt),
std::forward<Fs>(fs)...))>
(std::forward<ArgT>(argt), std::index_sequence_for<Fs...>{},
std::forward<Fs>(fs)...);}
Demo 2.
Building from the ground up and ignoring perfect forwarding so that I have to type less. We need a couple helpers. First, we need a partial version of apply that takes which indices from the tuple we want to apply to the function:
<class Tuple, class F, size_t... Is>
auto partial_apply(Tuple tuple, F f, std::index_sequence<Is...>) {
return f(get<Is>(tuple)...);
}
Then, we need to call that function for each subset of the tuple. Let's say we have all of our functions and indexes wrapped in a tuple already:
template <class Tuple, class FsTuple, class IsTuple, size_t... Is>
auto multi_apply(Tuple tuple, FsTuple fs, IsTuple indexes, std::index_sequence<Is...>) {
return std::make_tuple(
partial_apply(tuple,
std::get<Is>(fs),
std::get<Is>(indexes)
)...
);
}
So in this case, we'd want to end up calling multi_apply(tuple, <foo,bar,baz>, <<0,1>,<2,3,4>,<5,6,7>>, <0, 1, 2>).
All we need know is to build the indexes part. We're starting with <2,3,3>. We need to get the partial sums (<0,2,5>) and add that to the index sequences <<0,1>,<0,1,2>,<0,1,2>>. So we can write a partial sum function:
template <size_t I>
using size_t_ = std::integral_constant<size_t, I>;
template <class R, size_t N>
R partial_sum_(std::index_sequence<>, R, size_t_<N> ) {
return R{};
}
template <size_t I, size_t... Is, size_t... Js, size_t S>
auto partial_sum_(std::index_sequence<I, Is...>,
std::index_sequence<Js...>, size_t_<S> )
{
return partial_sum_(std::index_sequence<Is...>{},
std::index_sequence<Js..., S>{}, size_t_<S+I>{} );
}
template <size_t... Is>
auto partial_sum_(std::index_sequence<Is...> is)
{
return partial_sum_(is, std::index_sequence<>{}, size_t_<0>{} );
};
Which we can use to generate all of our indexes as a tuple:
template <size_t... Is, size_t N>
auto increment(std::index_sequence<Is...>, size_t_<N> )
{
return std::index_sequence<Is+N...>{};
}
template <class... Seqs, size_t... Ns>
auto make_all_indexes(std::index_sequence<Ns...>, Seqs... seqs)
{
return std::make_tuple(increment(seqs, size_t_<Ns>{})...);
}
Like so:
template <size_t... Is, class Tuple, class... Fs>
auto multiFunction(Tuple tuple, Fs... fs)
{
static_assert(sizeof...(Is) == sizeof...(Fs));
return multi_apply(tuple,
std::make_tuple(fs...),
make_all_indexes(
partial_sum_(std::index_sequence<Is...>{}),
std::make_index_sequence<Is>{}...
),
std::make_index_sequence<sizeof...(Is)>{}
);
}
If you want to handle void returns, then just make partial_apply return a tuple of a single element (or an empty tuple) and change the make_tuple() usage in multi_apply to tuple_cat().
Here's yet another impl:
template<std::size_t N>
constexpr Array<std::size_t, N> scan(std::size_t const (&a)[N])
{
Array<std::size_t, N> b{};
for (int i = 0; i != N - 1; ++i)
b[i + 1] = a[i] + b[i];
return b;
}
template<std::size_t O, std::size_t... N, class F, class Tuple>
inline decltype(auto) eval_from(std::index_sequence<N...>, F f, Tuple&& t)
{
return f(std::get<N + O>(std::forward<Tuple>(t))...);
}
template<std::size_t... O, std::size_t... N, class Tuple, class... F>
inline auto multi_function_impl1(std::index_sequence<O...>, std::index_sequence<N...>, Tuple&& t, F... f)
{
return pack(eval_from<O>(std::make_index_sequence<N>(), f, std::forward<Tuple>(t))...);
}
template<std::size_t... I, std::size_t... N, class Tuple, class... F>
inline auto multi_function_impl0(std::index_sequence<I...>, std::index_sequence<N...>, Tuple&& t, F... f)
{
constexpr std::size_t ns[] = {N...};
constexpr auto offsets = scan(ns);
return multi_function_impl1(std::index_sequence<offsets[I]...>(), std::index_sequence<N...>(), std::forward<Tuple>(t), f...);
}
template<std::size_t... N, class Tuple, class... F>
auto multi_function(Tuple&& t, F... f)
{
return multi_function_impl0(std::make_index_sequence<sizeof...(N)>(), std::index_sequence<N...>(), std::forward<Tuple>(t), f...);
}
where pack and Array are similar to std::make_tuple and std::array respectively, but to overcome some problems:
std::make_tuple decays it args, so references are lost
std::array cannot have its elems written in constexpr in c++14
DEMO
Here's my solution after following T.C.'s advice, adding to my previous (albeit inefficient) solution:
#include <iostream>
#include <tuple>
#include <utility>
struct NoReturnValue {
friend std::ostream& operator<< (std::ostream& os, const NoReturnValue&) {
return os << "[no value returned]";
}
};
template <std::size_t N, typename Tuple>
struct TupleHead {
static auto get (const Tuple& tuple) { // The subtuple from the first N components of tuple.
return std::tuple_cat (TupleHead<N-1, Tuple>::get(tuple), std::make_tuple(std::get<N-1>(tuple)));
}
};
template <typename Tuple>
struct TupleHead<0, Tuple> {
static auto get (const Tuple&) { return std::tuple<>{}; }
};
template <std::size_t N, typename Tuple>
struct TupleTail {
static auto get (const Tuple& tuple) { // The subtuple from the last N components of tuple.
return std::tuple_cat (std::make_tuple(std::get<std::tuple_size<Tuple>::value - N>(tuple)), TupleTail<N-1, Tuple>::get(tuple));
}
};
template <typename Tuple>
struct TupleTail<0, Tuple> {
static auto get (const Tuple&) { return std::tuple<>{}; }
};
template <typename Tuple, typename F, std::size_t... Is>
auto functionOnTupleHelper (const Tuple& tuple, F f, const std::index_sequence<Is...>&,
std::enable_if_t< !std::is_void<std::result_of_t<F(std::tuple_element_t<Is, Tuple>...)>>::value >* = nullptr) { // This overload is called only if f's return type is not void.
return std::make_tuple(f(std::get<Is>(tuple)...)); // Thanks to T.C.'s advice on returning a single tuple and then calling std::tuple_cat on all the single tuples.
}
template <typename Tuple, typename F, std::size_t... Is>
auto functionOnTupleHelper (const Tuple& tuple, F f, const std::index_sequence<Is...>&,
std::enable_if_t< std::is_void<std::result_of_t<F(std::tuple_element_t<Is, Tuple>...)>>::value >* = nullptr) { // This overload is called only if f's return type is void.
f(std::get<Is>(tuple)...);
return std::tuple<NoReturnValue>(); // Thanks to T.C.'s advice on returning std::tuple<NoReturnValue>() if the return type of 'f' is void.
}
template <typename Tuple, typename F>
auto functionOnTuple (const Tuple& tuple, F f) {
return functionOnTupleHelper (tuple, f, std::make_index_sequence<std::tuple_size<Tuple>::value>{});
}
template <typename Tuple, typename... Functions> struct MultiFunction;
template <typename Tuple, typename F, typename... Fs>
struct MultiFunction<Tuple, F, Fs...> {
template <std::size_t I, std::size_t... Is>
static inline auto execute (const Tuple& tuple, const std::index_sequence<I, Is...>&, F f, Fs... fs) {
const auto headTuple = TupleHead<I, Tuple>::get(tuple);
const auto tailTuple = TupleTail<std::tuple_size<Tuple>::value - I, Tuple>::get(tuple);
const auto r = functionOnTuple(headTuple, f); // Which overload of 'functionOnTupleHelper' is called dedends on whether f's return type is void or not.
return std::tuple_cat (r, MultiFunction<std::remove_const_t<decltype(tailTuple)>, Fs...>::execute (tailTuple, std::index_sequence<Is...>{}, fs...)); // T.C.'s idea of tuple_cat with all the single return tuples.
}
};
template <>
struct MultiFunction<std::tuple<>> {
static auto execute (const std::tuple<>&, std::index_sequence<>) { return std::tuple<>(); }
};
template <std::size_t... Is, typename Tuple, typename... Fs>
auto multiFunction (const Tuple& tuple, Fs... fs) {
return MultiFunction<Tuple, Fs...>::execute (tuple, std::index_sequence<Is...>{}, fs...);
}
// Testing
template <typename T> int foo (int, char) {std::cout << "foo<T>\n"; return 0;}
double bar (bool, double, long) {std::cout << "bar\n"; return 3.5;}
double bar (bool, int) {return 1.4;}
void voidFunction() {std::cout << "voidFunction\n";}
template <int...> bool baz (char, short, float) {std::cout << "baz<int...>\n"; return true;}
int main() {
const auto tuple = std::make_tuple(5, 'a', true, 3.5, 1000, 't', 2, 5.8);
const auto firstBar = [](bool b, double d, long l) {return bar(b, d, l);};
const auto t = multiFunction<2,3,0,3> (tuple, foo<bool>, firstBar, voidFunction, baz<2,5,1>); // Note that since 'bar' has an overload, we have to define 'firstBar' to indicate which 'bar' function we want to use.
std::cout << std::boolalpha << std::get<0>(t) << ' ' << std::get<1>(t) << ' ' << std::get<2>(t) << ' ' << std::get<3>(t) << '\n';
// 0 3.5 [no value returned] true
}
This solution should have linear time complexity. It uses std::tie instead of std::make_tuple, so neither the functions nor the arguments are copied unnecessarily. I think it should be fairly easy to follow compared to some other answers here.
First, we need a utility to invoke a function using a std::tuple of arguments.
template <typename F, typename Args, std::size_t... Is>
auto invoke_impl(F const& f, Args const& args, std::index_sequence<Is...>)
{
return f(std::get<Is>(args)...);
}
template <typename F, typename Args>
auto invoke(F const& f, Args const& args)
{
return invoke_impl(f, args, std::make_index_sequence<std::tuple_size<Args>::value>());
}
Secondly, we need a utility to std::tie a sub-range of tuple elements.
template <std::size_t Offset, typename Tuple, std::size_t... Is>
auto sub_tie_impl(Tuple const& tuple, std::index_sequence<Is...>)
{
return std::tie(std::get<Offset + Is>(tuple)...);
}
template <std::size_t Offset, std::size_t Count, typename Tuple>
auto sub_tie(Tuple const& tuple)
{
return sub_tie_impl<Offset>(tuple, std::make_index_sequence<Count>());
}
Now we can create our utility to consume a std::tuple of arguments using a sequence of functions.
First we std::tie the functions into a tuple, then we split the argument list into a parameter pack of sub-argument lists, and finally we invoke a function for each sub-argument list, packing the results into a tuple which we then return.
template <typename Fs, std::size_t... Is, typename... SubArgs>
auto consume_impl(Fs const& fs, std::index_sequence<Is...>, SubArgs const&... sub_args)
{
return std::make_tuple(invoke(std::get<Is>(fs), sub_args)...);
}
template <std::size_t, typename Args, typename Fs, typename... SubArgs>
auto consume_impl(Args const&, Fs const& fs, SubArgs const&... sub_args)
{
return consume_impl(fs, std::make_index_sequence<sizeof...(SubArgs)>(), sub_args...);
}
template <std::size_t Offset, std::size_t Count, std::size_t... Counts,
typename Args, typename Fs, typename... SubArgs>
auto consume_impl(Args const& args, Fs const& fs, SubArgs const&... sub_args)
{
return consume_impl<Offset + Count, Counts...>(args, fs, sub_args...,
sub_tie<Offset, Count>(args));
}
template <std::size_t... Counts, typename Args, typename... Fs>
auto consume(Args const& args, Fs const&... fs)
{
return consume_impl<0, Counts...>(args, std::tie(fs...));
}
Here's another solution borrowing Barry's partial_apply idea but avoiding the use of his partial_sum function altogether. It is shorter as a result. I think this is linear in time complexity.
#include <iostream>
#include <tuple>
#include <utility>
template <std::size_t Offset, typename F, typename Tuple, std::size_t... Is>
auto partial_apply_impl (F f, const Tuple& tuple, const std::index_sequence<Is...>&) {
return f(std::get<Offset + Is>(tuple)...);
}
template <typename Off, typename F, typename Tuple> // Off must be of type OffsetIndexSequence<A,B> only.
auto partial_apply (F f, const Tuple& tuple) {
return partial_apply_impl<Off::value>(f, tuple, typename Off::sequence{});
}
template <std::size_t Offset, std::size_t Size>
struct OffsetIndexSequence : std::integral_constant<std::size_t, Offset> {
using sequence = std::make_index_sequence<Size>;
};
template <typename Output, std::size_t... Is> struct OffsetIndexSequenceBuilder;
template <template <typename...> class P, typename... Out, std::size_t Offset, std::size_t First, std::size_t... Rest>
struct OffsetIndexSequenceBuilder<P<Out...>, Offset, First, Rest...> :
OffsetIndexSequenceBuilder<P<Out..., OffsetIndexSequence<Offset, First>>, Offset + First, Rest...> {};
template <template <typename...> class P, typename... Out, std::size_t Offset>
struct OffsetIndexSequenceBuilder<P<Out...>, Offset> {
using type = P<Out...>;
};
template <std::size_t... Is>
using offset_index_sequences = typename OffsetIndexSequenceBuilder<std::tuple<>, 0, Is...>::type;
template <typename> struct MultiFunction;
template <template <typename...> class P, typename... Offs>
struct MultiFunction<P<Offs...>> {
template <typename ArgsTuple, typename... Fs>
static auto execute (const ArgsTuple& argsTuple, Fs... fs) {
using ResultTuple = std::tuple<decltype(partial_apply<Offs>(fs, argsTuple))...>;
return ResultTuple{partial_apply<Offs>(fs, argsTuple)...};
}
};
template <std::size_t... Is, typename ArgsTuple, typename... Fs>
auto multiFunction (const ArgsTuple& argsTuple, Fs... fs) {
return MultiFunction<offset_index_sequences<Is...>>::execute(argsTuple, fs...);
}
// Testing
int foo (int, char) {std::cout << "foo\n"; return 0;}
double bar (bool, double, long) {std::cout << "bar\n"; return 3.5;}
bool baz (char, short, float) {std::cout << "baz\n"; return true;}
int main() {
const auto tuple = std::make_tuple(5, 'a', true, 3.5, 1000, 't', 2, 5.8);
const std::tuple<int, double, bool> t = multiFunction<2,3,3> (tuple, foo, bar, baz); // foo bar baz
std::cout << std::boolalpha << std::get<0>(t) << ' ' << std::get<1>(t) << ' ' << std::get<2>(t) << '\n'; // 0 3.5 true
}
Consider the following code:
#include <iostream>
#include <array>
template <typename, int, int...> struct NArray;
template <typename T, int NUM_DIMENSIONS, int N>
struct NArray<T, NUM_DIMENSIONS, N> {
using type = std::array<T, N>;
};
template <typename T, int NUM_DIMENSIONS, int FIRST, int... REST>
struct NArray<T, NUM_DIMENSIONS, FIRST, REST...> {
using type = std::array<typename NArray<T, NUM_DIMENSIONS, REST...>::type, FIRST>;
};
template <typename T, int NUM_DIMENSIONS, int... N>
typename NArray<T, NUM_DIMENSIONS, N...>::type NDimensionalArray() {
typename NArray<T, NUM_DIMENSIONS, N...>::type nArray;
return nArray;
}
int main() {
const auto nArray = NDimensionalArray<int,4, 2,4,5,3>();
}
What I want is to be able to extend the template pack of NDimensionalArray with more int values so that certain values are initialized to some specified fixed value. For example,
auto a = NDimensionalArray<bool,4, 2,4,5,3, 1,2,3,2, 0,0,2,1>(true);
will return a 2x4x5x3 4-dimensional std::array with a[1][2][3][2] = true and a[0][0][2][1] = true, and every other element false. But I'm having issues with multiple template packs and can't seem to get it working. Any help would be appreciated. Thanks.
Well here's a working solution. If somebody can improve upon it, I would be very interested in seeing it because I don't know any other way to do it.
#include <iostream>
#include <array>
#include <cstring>
template <int... > struct seq {};
template <typename, int...> struct NArray;
template <typename T, int N>
struct NArray<T, N> {
using type = std::array<T, N>;
};
template <typename T, int FIRST, int... REST>
struct NArray<T, FIRST, REST...> {
using type = std::array<typename NArray<T, REST...>::type, FIRST>;
};
template <typename T, typename Dim>
struct make_narray;
template <typename T, int... N>
struct make_narray<T, seq<N...>>
{
using type = typename NArray<T, N...>::type;
};
template <typename T>
T& get(T& val, seq<>)
{
return val;
}
template <typename NA, int E0, int... Es>
auto get(NA& arr, seq<E0, Es...>)
-> decltype(get(arr[E0], seq<Es...>{}))
{
return get(arr[E0], seq<Es...>{});
}
template <typename T, typename Dim, typename... Elems>
typename make_narray<T, Dim>::type
NDimensionalArray(T val)
{
typename make_narray<T, Dim>::type narray{};
auto _{get(narray, Elems{}) = val ...}; // Quick initialization step!
return narray;
}
int main() {
auto a = NDimensionalArray<bool, seq<2, 4, 5, 3>, seq<1, 2, 3, 2>, seq<0, 0, 2, 1>>(true);
std::cout << std::boolalpha;
std::cout << a[0][0][0][0] << std::endl; // prints false
std::cout << a[1][2][3][2] << std::endl; // prints true
std::cout << a[0][0][2][1] << std::endl; // prints true
}
The exact syntax you wanted NDimensionalArray<bool,4, 2,4,5,3, 1,2,3,2, 0,0,2,1>(true), in both C++14 and C++11 (second demo):
#include <iostream>
#include <iomanip>
#include <array>
#include <tuple>
#include <utility>
#include <type_traits>
#include <cstddef>
template <typename, int, int...> struct NArray;
template <typename T, int NUM_DIMENSIONS, int N>
struct NArray<T, NUM_DIMENSIONS, N>
{
using type = std::array<T, N>;
};
template <typename T, int NUM_DIMENSIONS, int FIRST, int... REST>
struct NArray<T, NUM_DIMENSIONS, FIRST, REST...>
{
using type = std::array<typename NArray<T, NUM_DIMENSIONS, REST...>::type, FIRST>;
};
template <typename A, typename T>
void assign(A& arr, const T& value)
{
arr = value;
}
template <int I, int... Is, typename A, typename T>
void assign(A& arr, const T& value)
{
assign<Is...>(arr[I], value);
}
template <int SIZE, int PACK, int... Ind, typename T, typename A, std::size_t... Is>
auto set(const T& value, A& arr, std::index_sequence<Is...> seq)
-> std::enable_if_t<(SIZE*PACK == sizeof...(Ind))>
{
}
template <int SIZE, int PACK, int... Ind, typename T, typename A, std::size_t... Is>
auto set(const T& value, A& arr, std::index_sequence<Is...> seq)
-> std::enable_if_t<(SIZE*PACK < sizeof...(Ind))>
{
constexpr auto t = std::make_tuple(Ind...);
assign<std::get<PACK*SIZE+Is>(t)...>(arr, value);
set<SIZE, PACK+1, Ind...>(value, arr, seq);
}
template <typename T, int DIMS, int... N, std::size_t... Is>
auto make_narray(const T& value, std::index_sequence<Is...> seq)
{
constexpr auto t = std::make_tuple(N...);
typename NArray<T, DIMS, std::get<Is>(t)...>::type arr{};
set<DIMS, 1, N...>(value, arr, seq);
return arr;
}
template <typename T, int DIMS, int... N>
auto NDimensionalArray(const T& value)
{
return make_narray<T, DIMS, N...>(value, std::make_index_sequence<DIMS>{});
}
int main()
{
auto a = NDimensionalArray<bool,4, 2,4,5,3, 1,2,3,2, 0,0,2,1>(true);
std::cout << std::boolalpha;
std::cout << a[1][2][3][2] << std::endl; // ~~~~^
std::cout << a[0][0][2][1] << std::endl; // ~~~~~~~~~~~~^
std::cout << a[0][0][0][0] << std::endl; // (not set)
}
Output:
true
true
false
DEMO (C++14)
DEMO 2 (C++11)
Solution with the initializing positions in the argument pack ARGS&&... args instead:
#include <array>
#include <iostream>
#include <deque>
template <typename, std::size_t...> struct NArray;
template <typename T, std::size_t N>
struct NArray<T,N> {
using type = std::array<T,N>;
};
template <typename T, std::size_t First, std::size_t... Rest>
struct NArray<T, First, Rest...> {
using type = std::array<typename NArray<T, Rest...>::type, First>;
};
template <typename E, typename Container, typename T>
void assign (E& element, Container&&, const T& v) { element = v; }
template <typename Subarray, std::size_t N, typename Container, typename T>
void assign (std::array<Subarray, N>& narray, Container&& pos, const T& v) {
const std::size_t index = pos.front();
pos.pop_front();
assign (narray[index], pos, v);
}
template <typename T, int... Dimensions, typename... Args>
typename NArray<T, Dimensions...>::type NDimensionalArray (const T& value, Args&&... args) {
typename NArray<T, Dimensions...>::type narray{};
const auto initializer = {std::forward<Args>(args)...};
const int groupSize = sizeof...(Dimensions), numGroups = initializer.size() / groupSize;
for (std::size_t i = 0; i < numGroups; i++)
assign (narray, std::deque<std::size_t>(initializer.begin() + i*groupSize, initializer.begin() + (i+1)*groupSize), value);
return narray;
}
int main() {
const auto multiArray = NDimensionalArray<double, 5,6,7,8,9> (3.14, 1,2,3,2,4, 3,3,2,1,2, 0,1,3,1,2);
std::cout << multiArray[1][2][3][2][4] << '\n'; // 3.14
std::cout << multiArray[3][3][2][1][2] << '\n'; // 3.14
std::cout << multiArray[0][1][3][1][2] << '\n'; // 3.14
}
Here is Piotr's solution tidied up a bit, by removing his enable_if specializations and using the index trick once again instead. Also, I've generalized to the following example syntax for any number of set values:
makeNDimensionalArray<char, I<3,6,5,4>, I<2,4,3,2, 0,1,2,3, 1,2,4,3>, I<0,0,0,0, 2,3,1,2>, I<1,1,2,1>>('a','b','c')
where I<3,6,5,4> sets the multi-array's dimensions. Then I<2,4,3,2, 0,1,2,3, 1,2,4,3> sets those three indexed positions of the array to 'a', I<0,0,0,0, 2,3,1,2> sets those two indexed positions of the array to 'b', and so forth.
#include <iostream>
#include <array>
#include <tuple>
#include <utility>
template <typename, std::size_t, std::size_t...> struct NArray;
template <typename T, std::size_t NumDimensions, std::size_t N>
struct NArray<T, NumDimensions, N> {
using type = std::array<T, N>;
};
template <typename T, std::size_t NumDimensions, std::size_t First, std::size_t... Rest>
struct NArray<T, NumDimensions, First, Rest...> {
using type = std::array<typename NArray<T, NumDimensions, Rest...>::type, First>;
};
template <typename T, std::size_t... Dimensions>
using NDimensionalArray = typename NArray<T, sizeof...(Dimensions), Dimensions...>::type;
template <typename T, typename Dimensions> struct NArrayFromPack;
template <typename T, template <std::size_t...> class P, std::size_t... Dimensions>
struct NArrayFromPack<T, P<Dimensions...>> : NArray<T, sizeof...(Dimensions), Dimensions...> {
static constexpr std::size_t num_dimensions = sizeof...(Dimensions);
};
template <typename A, typename T>
void setArrayValue (A& a, const T& t) { a = t; }
template <std::size_t First, std::size_t... Rest, typename Array, typename T>
void setArrayValue (Array& array, const T& t) {
setArrayValue<Rest...>(array[First], t);
}
template <typename Indices, typename Sequence> struct InitializeArray;
template <template <std::size_t...> class P, std::size_t... Is, std::size_t... Js>
struct InitializeArray<P<Is...>, std::index_sequence<Js...>> {
template <typename Array, typename T>
static void execute (Array& array, const T& t) {
constexpr std::size_t GroupSize = sizeof...(Js), NumGroups = sizeof...(Is) / GroupSize;
set<GroupSize>(array, t, std::make_index_sequence<NumGroups>{});
}
private:
template <std::size_t GroupSize, typename Array, typename T, std::size_t... Ks>
static void set (Array& array, const T& t, std::index_sequence<Ks...>) {
const int dummy[] = {(do_set<Ks, GroupSize>(array, t), 0)...};
static_cast<void>(dummy);
}
template <std::size_t N, std::size_t GroupSize, typename Array, typename T>
static void do_set (Array& array, const T& t) {
constexpr std::size_t a[] = {Is...};
setArrayValue<a[N*GroupSize + Js]...>(array, t);
}
};
template <typename T, typename Dimensions, typename... Indices, typename... Args>
auto makeNDimensionalArray (const Args&... args) {
using A = NArrayFromPack<T, Dimensions>;
typename A::type array;
const int a[] = {(InitializeArray<Indices, std::make_index_sequence<A::num_dimensions>>::execute(array, args), 0)...};
static_cast<void>(a);
return array;
}
template <std::size_t...> struct I;
int main() {
const NDimensionalArray<char, 3,6,5,4> a = makeNDimensionalArray<char, I<3,6,5,4>, I<2,4,3,2, 0,1,2,3, 1,2,4,3>, I<0,0,0,0, 2,3,1,2>, I<1,1,2,1>>('a','b','c');
std::cout << a[2][4][3][2] << std::endl; // a
std::cout << a[0][1][2][3] << std::endl; // a
std::cout << a[1][2][4][3] << std::endl; // a
std::cout << a[0][0][0][0] << std::endl; // b
std::cout << a[2][3][1][2] << std::endl; // b
std::cout << a[1][1][2][1] << std::endl; // c
}
If I have a tuple with different element types like
std::tuple<T0, T1, T2, ...>
And how to get the index of a element type?
template<class T, class Tuple>
struct Index
{
enum {value = ?;}
};
Thanks.
template <class T, class Tuple>
struct Index;
template <class T, class... Types>
struct Index<T, std::tuple<T, Types...>> {
static const std::size_t value = 0;
};
template <class T, class U, class... Types>
struct Index<T, std::tuple<U, Types...>> {
static const std::size_t value = 1 + Index<T, std::tuple<Types...>>::value;
};
See it live at Coliru.
This implementation returns the index of the first occurrence of a given type. Asking for the index of a type that is not in the tuple results in a compile error (and a fairly ugly one at that).
template< size_t I, typename T, typename Tuple_t>
constexpr size_t index_in_tuple_fn(){
static_assert(I < std::tuple_size<Tuple_t>::value,"The element is not in the tuple");
typedef typename std::tuple_element<I,Tuple_t>::type el;
if constexpr(std::is_same<T,el>::value ){
return I;
}else{
return index_in_tuple_fn<I+1,T,Tuple_t>();
}
}
template<typename T, typename Tuple_t>
struct index_in_tuple{
static constexpr size_t value = index_in_tuple_fn<0,T,Tuple_t>();
};
The example above avoids generating tons of sub tuples, which makes compilation fail (out of memory) when you call index_in_tuple for large tuples
With constexpr "function" (or lambda), you might do
template <class T, class Tuple>
struct Index;
template <class T, typename... Ts>
struct Index<T, std::tuple<Ts...>>
{
static constexpr std::size_t index = [](){
constexpr std::array<bool, sizeof...(Ts)> a{{ std::is_same<T, Ts>::value... }};
// You might easily handle duplicate index too (take the last, throw, ...)
// Here, we select the first one.
const auto it = std::find(a.begin(), a.end(), true);
// You might choose other options for not present.
// As we are in constant expression, we will have compilation error.
// and not a runtime expection :-)
if (it == a.end()) throw std::runtime_error("Not present");
return std::distance(a.begin(), it);
}();
};
Actually requires C++20 as missing constexpr for std functions,
but can easily be rewritten for previous version. (C++11 would be trickier with the strong restriction for constexpr).
Yet another one using fold expression.
It also sets the value to -1 when not found.
template <class X, class Tuple>
class Idx;
template <class X, class... T>
class Idx<X, std::tuple<T...>> {
template <std::size_t... idx>
static constexpr ssize_t find_idx(std::index_sequence<idx...>) {
return -1 + ((std::is_same<X, T>::value ? idx + 1 : 0) + ...);
}
public:
static constexpr ssize_t value = find_idx(std::index_sequence_for<T...>{});
};
live: https://onlinegdb.com/SJE8kOYdv
EDIT:
As suggested by #Jarod42, one may use std::max:
template <class X, class Tuple>
class Idx;
template <class X, class... T>
class Idx<X, std::tuple<T...>> {
template <std::size_t... idx>
static constexpr ssize_t find_idx(std::index_sequence<idx...>) {
return std::max({static_cast<ssize_t>(std::is_same_v<X, T> ? idx : -1)...});
}
public:
static constexpr ssize_t value = find_idx(std::index_sequence_for<T...>{});
};
template<typename X, class Tuple>
inline constexpr ssize_t Idx_v = Idx<X, Tuple>::value;
In case of duplicate type, this version returns the index of the last one.
live: https://onlinegdb.com/WenEBQs0L
template <typename T, typename U, typename... Us>
constexpr auto getIndex() {
if constexpr (is_same_v<T, U>) {
return 0;
} else {
if constexpr (sizeof...(Us)) {
return 1 + getIndex<T, Us...>();
} else {}
}
}
template <typename T, typename U, typename... Us>
constexpr auto getIndex(const tuple<U, Us...> &) {
return getIndex<T, U, Us...>();
}
usage
tuple the_tuple{'\0', 1, 2L, 3.0, "4", string{"5"}};
cout << getIndex<char>(the_tuple) << endl; // 0
cout << getIndex<double>(the_tuple) << endl; // 3
cout << getIndex<const char *>(the_tuple) << endl; // 4
cout << getIndex<string>(the_tuple) << endl; // 5
/* cout << getIndex<short>(the_tuple) << endl; // compile error */
Try this one, which reports error if the tuple is empty, T doesn't exist or not unique in the tuple:
template <template <typename ...> class TT, std::size_t I, typename ...Ts>
struct defer
{
using type = TT<I, Ts...>;
};
template <std::size_t, typename, typename>
struct tuple_index_helper;
template <std::size_t I, typename T, typename U, typename ...Vs>
struct tuple_index_helper<I, T, std::tuple<U, Vs...>>
{
static_assert(!std::is_same_v<T, U>, "Type not unique.");
static constexpr std::size_t index = tuple_index_helper<I, T, std::tuple<Vs...>>::index;
};
template <std::size_t I, typename T>
struct tuple_index_helper<I, T, std::tuple<>>
{
static constexpr std::size_t index = I;
};
template <std::size_t, typename, typename>
struct tuple_index;
template <std::size_t I, typename T, typename U, typename ...Vs>
struct tuple_index<I, T, std::tuple<U, Vs...>>
{
static constexpr std::size_t index = std::conditional_t<std::is_same_v<T, U>, defer<tuple_index_helper, I, T, std::tuple<Vs...>>, defer<tuple_index, I + 1, T, std::tuple<Vs...>>>::type::index;
};
template <std::size_t I, typename T>
struct tuple_index<I, T, std::tuple<>>
{
static_assert(!(I == 0), "Empty tuple.");
static_assert(!(I != 0), "Type not exist.");
};
template <typename T, typename U>
inline constexpr std::size_t tuple_index_v = tuple_index<0, T, U>::index;
Example:
std::tuple<int, float, const char*> t1{};
std::tuple<int, float, int> t2{};
std::tuple<> t3{};
constexpr auto idx = tuple_index_v<float, decltype(t1)>; // idx = 1
// constexpr auto idx2 = tuple_index_v<long long, decltype(t1)> // Error: Type not exist.
// constexpr auto idx3 = tuple_index_v<int, decltype(t2)> // Error: Type not unique.
// constexpr auto idx4 = tuple_index_v<int, decltype(t3)> // Error: Empty tuple.
This does what Qiang does, but it doesn't have that strange looking empty else branch.
It also makes sure that a tuple with unique types gets passed to it for good measure.
template <typename...>
inline constexpr auto is_unique = std::true_type{};
template <typename T, typename... Rest>
inline constexpr auto is_unique<T, Rest...> = std::bool_constant<(!std::is_same_v<T, Rest> && ...) && is_unique<Rest...>>{};
template <typename T, typename U, typename... Us>
constexpr auto getIndexImpl() {
if constexpr (std::is_same<T, U>::value) {
return 0;
} else {
static_assert(sizeof...(Us) > 0, "This tuple does not have that type");
return 1 + getIndexImpl<T, Us...>();
}
}
template <typename T, typename U, typename... Us>
constexpr auto getIndex(const std::tuple<U, Us...> &) {
static_assert(is_unique<U, Us...>, "getIndex should only be called on tuples with unique types.");
return getIndexImpl<T, U, Us...>();
}
I have a template function with varargs template arguments, like this
template<typename Args...>
void ascendingPrint(Args... args) { /* ... */ }
And I want to write
template<typename Args...>
void descendingPrint(Args... args) {
/* implementation using ascendingPrint()? */
}
How do I reverse the order of the parameter-pack args before passing it along, i.e. in pseudo-code:
template<typename Args...>
void descendingPrint(Args... args) {
ascendingPrint( reverse(args) );
}
Overall approach and usage
The overal approach consists in packing the arguments into an std::tuple of references, exploiting the perfect forwarding machinery of std::forward_as_tuple().
This means that, at run-time, you should incur in very small overhead and no unnecessary copy/move operations. Also, the framework does not use recursion (apart from compile-time recursion, which is unavoidable for generating indices), so no risk of run-time overhead even in case the compiler would not manage to inline the recursive function calls (which is unlikely anyway, so this is more of an academic argument).
Moreover, this solution is general, in that you can use it as a header-only library to invoke your functions with reversed arguments and with minimum effort: descending_print() should be just a minimal thin wrapper around ascending_print().
Here is how it should look like:
MAKE_REVERT_CALLABLE(ascending_print)
template<typename... Args>
void descending_print(Args&&... args)
{
revert_call(REVERT_ADAPTER(ascending_print), std::forward<Args>(args)...);
}
What follows is a presentation of the implementation.
First step: reverting a type sequence
Here is a simple way to revert a type sequence:
#include <tuple>
#include <type_traits>
template<typename, typename>
struct append_to_type_seq { };
template<typename T, typename... Ts>
struct append_to_type_seq<T, std::tuple<Ts...>>
{
using type = std::tuple<Ts..., T>;
};
template<typename... Ts>
struct revert_type_seq
{
using type = std::tuple<>;
};
template<typename T, typename... Ts>
struct revert_type_seq<T, Ts...>
{
using type = typename append_to_type_seq<
T,
typename revert_type_seq<Ts...>::type
>::type;
};
A small test program:
int main()
{
static_assert(
std::is_same<
revert_type_seq<char, int, bool>::type,
std::tuple<bool, int, char>
>::value,
"Error"
);
}
And a live example.
Second step: reverting a tuple
The next step consists in reverting a tuple. Given the usual indices trick machinery:
template <int... Is>
struct index_list { };
namespace detail
{
template <int MIN, int N, int... Is>
struct range_builder;
template <int MIN, int... Is>
struct range_builder<MIN, MIN, Is...>
{
typedef index_list<Is...> type;
};
template <int MIN, int N, int... Is>
struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
{ };
}
template<int MIN, int MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;
Together with the functions defined above, a tuple can easily be reverted this way:
template<typename... Args, int... Is>
typename revert_type_seq<Args...>::type
revert_tuple(std::tuple<Args...> t, index_list<Is...>)
{
using reverted_tuple = typename revert_type_seq<Args...>::type;
// Forwarding machinery that handles both lvalues and rvalues...
auto rt = std::forward_as_tuple(
std::forward<
typename std::conditional<
std::is_lvalue_reference<
typename std::tuple_element<Is, reverted_tuple>::type
>::value,
typename std::tuple_element<Is, reverted_tuple>::type,
typename std::remove_reference<
typename std::tuple_element<Is, reverted_tuple>::type
>::type
>::type
>(std::get<sizeof...(Args) - Is - 1>(t))...
);
return rt;
}
template<typename... Args>
typename revert_type_seq<Args...>::type
revert_tuple(std::tuple<Args...> t)
{
return revert_tuple(t, index_range<0, sizeof...(Args)>());
}
Here is a simple test program:
#include <iostream>
int main()
{
std::tuple<int, int, char> t(42, 1729, 'c');
auto rt = revert_tuple(t);
std::cout << std::get<0>(rt) << " "; // Prints c
std::cout << std::get<1>(rt) << " "; // Prints 1729
std::cout << std::get<2>(rt) << " "; // Prints 42
}
Here is a live example.
Third step: reverting a function's arguments
The final step consists in unpacking the tuple when calling our target function. Here is another generic utility to save us a couple of lines:
template<typename... Args>
typename revert_type_seq<Args...>::type
make_revert(Args&&... args)
{
auto t = std::forward_as_tuple(std::forward<Args>(args)...);
return revert_tuple(t);
}
The above function creates a tuple whose elements are the arguments provided, but in reverse order. We are not ready to define our target:
template<typename T>
void ascending_print(T&& t)
{
std::cout << std::forward<T>(t) << " ";
}
template<typename T, typename... Args>
void ascending_print(T&& t, Args&&... args)
{
ascending_print(std::forward<T>(t));
ascending_print(std::forward<Args>(args)...);
}
The above function(s) prints all the arguments provided. And here is how we could write descending_print():
template<typename T, int... Is>
void call_ascending_print(T&& t, index_list<Is...>)
{
ascending_print(std::get<Is>(std::forward<T>(t))...);
}
template<typename... Args>
void descending_print(Args&&... args) {
call_ascending_print(make_revert(std::forward<Args>(args)...),
index_range<0, sizeof...(Args)>());
}
A simple test case again:
int main()
{
ascending_print(42, 3.14, "Hello, World!");
std::cout << std::endl;
descending_print(42, 3.14, "Hello, World!");
}
And of course a live example.
Final step: simplification
The above solution may be non-trivial to understand, but it can be made trivial to use, and quite flexible. Given a couple of generic functions:
template<typename F, typename... Args, int... Is>
void revert_call(F&& f, index_list<Is...>, Args&&... args)
{
auto rt = make_revert(std::forward<Args>(args)...);
f(std::get<Is>(rt)...);
}
template<typename F, typename... Args>
void revert_call(F&& f, Args&&... args)
{
revert_call(f, index_range<0, sizeof...(Args)>(),
std::forward<Args>(args)...);
}
And a couple of macro definitions (I couldn't find a way to create an overload set for a function template, sorry):
#define MAKE_REVERT_CALLABLE(func) \
struct revert_caller_ ## func \
{ \
template<typename... Args> void operator () (Args&&... args) \
{ func(std::forward<Args>(args)...); } \
};
#define REVERT_ADAPTER(func) \
revert_caller_ ## func()
It becomes really easy to adapt any function for being called with arguments in reverse order:
MAKE_REVERT_CALLABLE(ascending_print)
template<typename... Args>
void descending_print(Args&&... args)
{
revert_call(REVERT_ADAPTER(ascending_print), std::forward<Args>(args)...);
}
int main()
{
ascending_print(42, 3.14, "Hello, World!");
std::cout << std::endl;
descending_print(42, 3.14, "Hello, World!");
}
To conclude, as usual, a live example.
I think instead of reversing the arguments, you can reverse your logic! For example reverse the operations on arguments.
template <typename T>
void ascendingPrint(const T& x)
{
cout << x << " ";
}
template<typename T, typename ... Args>
void ascendingPrint(const T& t, Args... args)
{
ascendingPrint(t); // First print `t`
ascendingPrint(args...); // Then print others `args...`
}
template <typename T>
void descendingPrint(const T& x)
{
cout << x << " ";
}
template<typename T, typename ... Args>
void descendingPrint(const T& t, Args... args)
{
descendingPrint(args...); // First print others `args...`
descendingPrint(t); // Then print `t`
}
and then
int main()
{
ascendingPrint(1, 2, 3, 4);
cout << endl;
descendingPrint(1, 2, 3, 4);
}
Output
1 2 3 4
4 3 2 1
Here's the simple approach I mentioned in the comments: Generating indices in reverse and unpacking a tuple with that.
// reversed indices...
template<unsigned... Is> struct seq{ using type = seq; };
template<unsigned I, unsigned... Is>
struct rgen_seq : rgen_seq<I-1, Is..., I-1>{};
template<unsigned... Is>
struct rgen_seq<0, Is...> : seq<Is...>{};
#include <tuple>
namespace aux{
template<class Tup, unsigned... Is>
void descending_print(Tup&& t, seq<Is...>)
{
ascending_print(std::get<Is>(std::forward<Tup>(t))...);
}
} // aux::
template<class... Args>
void descending_print(Args&&... args)
{
auto t = std::forward_as_tuple(std::forward<Args>(args)...);
aux::descending_print(t, rgen_seq<sizeof...(Args)>{});
}
Live example.
Here is a recursive implementation of a specialized revert<>:
// forward decl
template<class ...Tn>
struct revert;
// recursion anchor
template<>
struct revert<>
{
template<class ...Un>
static void apply(Un const&... un)
{
ascendingPrint(un...);
}
};
// recursion
template<class T, class ...Tn>
struct revert<T, Tn...>
{
template<class ...Un>
static void apply(T const& t, Tn const&... tn, Un const&... un)
{
// bubble 1st parameter backwards
revert<Tn...>::apply(tn..., t, un...);
}
};
// using recursive function
template<class A, class ...An>
void descendingPrint(A const& a, An const&... an)
{
revert<An...>::apply(an..., a);
}
It works with gcc-4.6/7/8 and clang and is probably standard compliant -- the only difficult part being the call of revert<Tn...>::apply(tn..., t, un...).
It has drawbacks though (as recursion often has), that it generates a lot of template-instantiations of the target function (code bloat) and does not use perfect forwarding, which may be an issue (but maybe could be improved to use it).
My solution supports perfect forwarding and does not involve a recursion:
#include <iostream>
#include <utility>
#include <tuple>
#include <cstdlib>
template< typename ...types >
void
ascendingPrint(types &&... _values)
{
(std::cout << ... << std::forward< types >(_values)) << std::endl;
}
template< typename ...types, std::size_t ...indices >
void
descendingPrintHelper(std::tuple< types... > const & refs, std::index_sequence< indices... >)
{
constexpr std::size_t back_index = sizeof...(indices) - 1;
return ascendingPrint(std::forward< std::tuple_element_t< back_index - indices, std::tuple< types... > > >(std::get< back_index - indices >(refs))...);
}
template< typename ...types >
void
descendingPrint(types &&... _values)
{
auto const refs = std::forward_as_tuple(std::forward< types >(_values)...);
return descendingPrintHelper(refs, std::make_index_sequence< sizeof...(types) >{});
}
int
main()
{
ascendingPrint(1, ' ', 2, ' ', 3);
descendingPrint(1, ' ', 2, ' ', 3);
return EXIT_SUCCESS;
}
Live example (or even simplier).
Also modern compilers can perfectly optimize out all the unnecessary stuff: https://godbolt.org/g/01Qf6w
This can be done using C++17 fold expression and a little trick for right-to-left order execution.
#include <iostream>
template< typename T> void print(T&& val) { std::cout << val; }
template< typename ... Types > void descendingPrint(Types&&... vals) {
int tmps = 0;
((print(vals), tmps) = ...);
}
int main() {
descendingPrint(1, ' ', 2, ' ', 3);
return 0;
}