Iterate std::tuple indices with lambda [duplicate] - c++

This question already has an answer here:
Achieving 'constexpr for' with indexing
(1 answer)
Closed 5 months ago.
There are a lot of approaches how to iterate trough std::tuple. And it is similar to range-based-for loop. I want to do something like this, but with indices of tuple, to get access to elements of various tuples.
For example I have tuple of different types, but all of them has same free functions / operators std::tuple<float, std::complex, vec3, vec4> and I want to do some operation between two or more such tuples.
I tried to write something like this:
template<typename Lambda, typename... Types, int... Indices>
void TupleIndexElems_Indexed(TTuple<Types...>, Lambda&& Func, TIntegerSequence<int, Indices...>)
{
Func.template operator()<Indices...>();
}
template<typename TupleType, typename Lambda>
void TupleIndexElems(Lambda&& Func)
{
TupleIndexElems_Impl(TupleType{}, Func);
}
template<typename... Types, typename Lambda>
void TupleIndexElems_Impl(TTuple<Types...>, Lambda&& Func)
{
TupleIndexElems_Indexed(TTuple<Types...>{}, Func, TMakeIntegerSequence<int, sizeof...(Types)>{});
}
Usage:
FSkyLightSettings& operator+=(FSkyLightSettings& Other)
{
auto Tup1 = AsTuple();
auto Tup2 = Other.AsTuple();
using TupType = TTuple<float*, FLinearColor*, FLinearColor*>;
auto AddFunc = [] <typename Tup, int Index> (Tup t1, Tup t2)
{
*t1.template Get<Index>() = (*t1.template Get<Index>()) + (*t2.template Get<Index>());
};
TupleIndexElems<TupType>([=]<int... Indices>
{
AddFunc.template operator()<TupType, Indices>(Tup1, Tup2); // How to fold it?
});
return *this;
}
I thought the best way to do it is using variaic lambda template, but when I tried to call it, I confused about impossibility to use fold expression.
Are there any elegant solutions to do that (for various versions of C++)?
UPD: I've also tried to use recursive lambda, but I can't due to compiler error C3536:
auto PlusVariadic = [=]<int Index, int... Indices>
{
Plus.template operator()<TupType, Index>(Tup1, Tup2); // How to fold it?
if constexpr (Index != 0)
{
PlusVariadic.operator()<Indices...>();
}
};

One convenient way in C++20 I use to iterate tuples is to create a constexpr_for function that calls a lambda with a std::integral_constant parameter to allow indexing, as described in my Achieving 'constexpr for' with indexing post.
#include <utility>
#include <type_traits>
template<size_t Size, typename F>
constexpr void constexpr_for(F&& function) {
auto unfold = [&]<size_t... Ints>(std::index_sequence<Ints...>) {
(std::forward<F>(function)(std::integral_constant<size_t, Ints>{}), ...);
};
unfold(std::make_index_sequence<Size>());
}
example usage:
#include <tuple>
#include <iostream>
int main() {
auto Tup1 = std::make_tuple(1, 2.0, 3ull, 4u);
auto Tup2 = std::make_tuple(1ull, 2.0f, 3.0, (char)4);
constexpr auto size = std::tuple_size_v<decltype(Tup1)>;
constexpr_for<size>([&](auto i) {
std::get<i>(Tup1) += std::get<i>(Tup2);
std::cout << "tuple<" << i << "> = " << std::get<i>(Tup1) << '\n';
});
}
Output:
tuple<0> = 2
tuple<1> = 4
tuple<2> = 6
tuple<3> = 8
Try it out on godbolt.

Related

how to specify C++ callable concept when the callable parameters types are deduced

I invoke a templated lambda from a templated function, the lambda parameters type are deduced. If the type of the lambda if auto, it works :
https://godbolt.org/z/WYxj5G8vx
#include <iostream>
#include <cstdint>
#include <array>
#include <functional>
#include <numeric>
#include <concepts>
template <typename T>
int testf2(T, auto fun) {
std::array<std::uint8_t, sizeof(T)> ar{};
std::iota(ar.begin(), ar.end(), 0);
return fun(ar);
}
int main() {
auto f2 = []<size_t S> (std::array<uint8_t, S> arr) -> int {
return arr[S -1];
};
std::cout << "R = " << testf2(5, f2) << std::endl;
}
I wanted to use std::invocable concept to specialize the auto fun parameter of testf2, to be anything but a callable that take std::array<std::uint8_t, N> as parameter.
Using gcc11.2 or clang13, when I try
template <typename T, size_t S>
int testf2(T, std::invocable<std::array<uint8_t, S>> auto fun) {
std::array<std::uint8_t, sizeof(T)> ar{};
std::iota(ar.begin(), ar.end(), 0);
return fun(ar);
}
I get error :
candidate template ignored: couldn't infer
template argument 'S' int testf2(T, std::invocable<std::array<uint8_t,
S>> auto fun) {
I don't understand why the compiler can infer type when only auto is used, but not with a constraining concept.
What is the correct way to use concept in this situation ?
This is a simplified version of the code, in reality the signature of testf2 is testf2(auto fun, ARGS... args) and the size of the array is calculated upon the parameter pack types.
============ EDIT 03/03/2022 ==================
Thanks for the correct answers, but I have oversimplified the code and the question, so I get right answer to a wrong question.
You need more context, I work with MCUs, and want to make a function that abstract some kind of spi,i2c,modbus, etc transaction where one send buffer to the slave peripheral and receive buffer in response. The function calculate write and read buffer length, serialise (doing endianness conversion if needed), call a lambda to do the actual transaction depending on the transport mechanism, deserialise and return. So the buffers lengths cannot be calculated with a (sizeof(Ts) + ...) as suggested.
I made a more realistic example :live example
// return empty array whose size is the sum of the two arrays given as parameters
template<typename T, std::size_t LL, std::size_t RL>
constexpr std::array<T, LL+RL> join(std::array<T, LL>, std::array<T, RL>)
{
return std::array<T, LL+RL>{};
}
// return an array of size sizeof(T) if T is arithmetic, otherwise an empty array
template <typename T>
constexpr auto count_ari(T) {
if constexpr (std::is_arithmetic_v<T>) {
return std::array<uint8_t, sizeof(T)>{};
} else {
return std::array<uint8_t, 0>{};
}
}
// return empty array whose size is the sum of all parameter which are arithmetic
template <typename HEAD, typename... TAIL>
constexpr auto count_ari(HEAD h, TAIL... tail) {
return join(count_ari(h), count_ari(tail...));
}
// create a iota filled array whose size is sum of all arithmetic parameters
// call a lambda given in parameter on this array
// return what has done the lambda
// it's here that I want to constrain parameter "auto fun"
template </*size_t S,*/ typename... ARGS>
int testf2(/*std::invocable<std::array<uint8_t, S>>, */ auto fun, ARGS... args) {
auto ar = count_ari(args...);
std::iota(ar.begin(), ar.end(), 1);
return fun(ar);
}
int main() {
auto f2 = []<size_t S> (std::array<uint8_t, S> arr) -> int {
return arr[S -1];
};
std::cout << "R = " << testf2(f2, 'a') << std::endl;
std::cout << "R = " << testf2(f2, 6, 7l, "foobar") << std::endl;
}
Question remains the same : is there a way to add constrain on the auto fun parameter of function testf2
Concepts (and requires clauses in general) do not participate in template argument deduction. Since your S in this case is just sizeof(T), you should use that.
the size S is the sum of all the sizes of the types of a parameter pack
Then make it sizeof(Args) + ....
You can use delcltype(count_ari(args...)) to get the resulting array type as the template parameter of std::invocable:
template <typename... ARGS>
int testf2(
std::invocable<decltype(count_ari(std::declval<ARGS>()...))> auto fun,
ARGS... args);
Or
template <typename... ARGS>
int testf2(auto fun, ARGS... args)
requires std::invocable<decltype(fun), decltype(count_ari(args...))>;
Demo
Nicol Bolas helps me to find the solution which was to make a constexpr function taking no argument that calculate the size, and specify the exact type of the callable with std::function instead of trying to specialise auto with invocable concept.
template <typename T>
constexpr size_t sizeof_ari() {
if constexpr (std::is_arithmetic_v<T>)
return sizeof(T);
else
return 0;
}
template <typename... ARGS>
constexpr size_t sizeof_aris() {
return (sizeof_ari<ARGS>() + ...);
}
// create a iota filled array whose size is sum of all arithmetic parameters
// call a lambda given in parameter on this array
// return what has done the lambda
template <typename... ARGS>
using lambda_param = std::array<uint8_t, sizeof_aris<ARGS...>()>;
template <typename... ARGS>
int testf2(std::function<int(lambda_param<ARGS...>)> fun, ARGS... args) {
auto ar = make_buf(args...);
std::iota(ar.begin(), ar.end(), 1);
return fun(ar);
}
demo

Apply stateful lambda to integer sequence values

I am playing around with trying to implement the numeric literal operator template.
#include <string_view>
#include <cstdint>
#include <cmath>
#include <iostream>
#include <boost/mp11/integer_sequence.hpp>
#include <boost/mp11/algorithm.hpp>
using namespace boost::mp11;
template <char... Cs>
[[nodiscard]] constexpr auto operator""_c(){
int weight =std::pow(10, sizeof... (Cs));
// unused, would like to transform it using lambda that mutably captures
// weight
using ints = index_sequence<sizeof... (Cs)>;
// ugly fold way
auto val = ((weight/=10,(int)(Cs-'0')*weight) + ...);
return val;
}
int main(){
std::cout << 0_c << std::endl;
std::cout << 00_c << std::endl;
std::cout << 01_c << std::endl;
std::cout << 123_c << std::endl;
}
This code works for simple cases(correctness is not important, e.g. negative numbers), it is just an example, but code looks ugly and clang emits a warning for modifying weight multiple times, so I guess code is buggy(undefined or unspecified behavior) although it seems to work...
Now I wonder is there a way for me to transform the ints I use(it is from boost::mp11, but same thing exists in std::) with a stateful lambda (that modifies weight).
So I would like to transfer ints, that are <0,1,2> into something like <100,10,1>
I presume this has been asked before but this is very hard to search for.
To be clear: operator "" is just a toy problem, my real question is about mapping the values of integer sequence with a stateful lambda.
Also if not clear from question: I am perfectly happy to use boost mp11, but could not find anything in the docs.
So I would like to transfer ints, that are <0,1,2> into something like
<100,10,1>
First, you can convert std::index_sequence to std::array, then perform your operations on it as you normally do, and finally, convert std::array to std::index_sequence again.
In order for the stateful lambda to work at compile-time, we can accept a function that can return the stateful lambda then get it internally:
template<std::size_t... Is>
constexpr auto transform_seq(std::index_sequence<Is...>, auto get_op) {
// index_sequence -> array
constexpr auto arr = [op = get_op()]() mutable {
std::array<std::size_t, sizeof...(Is)> arr{Is...};
for (auto& value : arr)
value = op(value);
return arr;
}();
// array -> index_sequence
constexpr auto seq = [&]<std::size_t... Js>(std::index_sequence<Js...>) {
return std::index_sequence<std::get<Js>(arr)...>{};
}(std::make_index_sequence<arr.size()>{});
return seq;
};
Then you can perform the index_sequence conversion according to op you pass in:
using input1 = std::index_sequence<0,1,2>;
auto gen_op1 = [] {
return [w = 1000](auto x) mutable { w /= 10; return w; };
};
using res1 = decltype(transform_seq(input1{}, gen_op1));
static_assert(std::same_as<res1, std::index_sequence<100, 10, 1>>);
using input2 = std::index_sequence<0,1,2,3>;
auto gen_op2 = [] {
return [b = true] (auto x) mutable { b = !b; return b * 10 + x; };
};
using res2 = decltype(transform_seq(input2{}, gen_op2));
static_assert(std::same_as<res2, std::index_sequence<0,11,2,13>>);
Demo.
I think you want:
template <typename F, std::size_t ... Is>
constexpr auto apply(F f, std::index_sequence<Is...>)
-> std::index_sequence<f(Is)...>
{
return {};
}
template <char... Cs>
[[nodiscard]] constexpr auto operator""_c(){
return []<std::size_t ... Pows>(std::index_sequence<Pows...>){
return ((Pows * (Cs - '0')) + ...);
}(apply([](std::size_t n){ return ipow(10, sizeof...(Cs) - n - 1);},
std::make_index_sequence<sizeof...(Cs)>()));
}
Demo
But doing computation directly seems even simpler:
template <char... Cs>
[[nodiscard]] constexpr auto operator""_c(){
constexpr auto res =
[]<std::size_t ... Is>(std::index_sequence<Is...>){
return ((ipow(10, sizeof...(Cs) - Is - 1) * (Cs - '0')) + ...);
}(std::make_index_sequence<sizeof...(Cs)>());
return res;
}

Filter a tuple of types in c++17

std::tuple a{1,3,4,5} -> make it to numbers greater than 3
std::tuple b{4,5}
Or
std::tuple a{
std::integral_constant<int,1> {},
std::integral_constant<int,3> {},
std::integral_constant<int,4> {},
std::integral_constant<int,5> {}
}
to
std::tuple a{
std::integral_constant<int,4>{},
std::integral_constant<int,5>{}
};
How to convert this at compile time? I can do this using integer_sequence but that is a cumbersome. Is there a simpler way in C++17 using fold expressions or std::apply
Also after filter, also need to get a tuple of unique entries. But my assumption is if filtering can be done, then finding unique would be trivial.
Edit so that is more clear:
std::tuple<int_c<1>, int_c<3>,int_c<4>,int_c<5>> to std::tuple<int_c<4>,int_c<5> <-- If such is possible in a concise c++17 way without extra declare functions, it would do!.
Edit:
I was fiddling around, maybe something like this would work:
with template... C as the list of integrals constants:
constexpr auto result = std::tuple_cat(std::conditional_t<(C::value > 3), std::tuple<C>, std::tuple<>>{}...);
To turn out your tuple_cat with c++17:
constexpr auto result = std::apply([](auto...ts) {
return std::tuple_cat(std::conditional_t<(decltype(ts)::value > 3),
std::tuple<decltype(ts)>,
std::tuple<>>{}...);
}, tup);
A possible solution is to produce a trait that will output std::tuple<T> for desirable elements T and std::tuple<> for undesirable elements and to use std::tuple_cat to recombine those tuples into a single type. For example :
#include <tuple>
#include <type_traits>
#include <utility>
template <typename Pred, typename Tuple> struct filter;
template <typename t_Predicate, typename ...Ts>
struct filter<t_Predicate, std::tuple<Ts...>>
{
// If this element has to be kept, returns `std::tuple<Ts>`
// Otherwise returns `std::tuple<>`
template<class E>
using t_filter_impl = std::conditional_t<
t_Predicate<E>::value,
std::tuple<E>, std::tuple<>>;
// Determines the type that would be returned by `std::tuple_cat`
// if it were called with instances of the types reported by
// t_filter_impl for each element
using type = decltype(std::tuple_cat(std::declval<t_filter_impl<Ts>>()...));
};
Where t_Predicate<T> is any predicate type with a bool value; member which determines whether or not T is a desirable type. For example to apply this solution to the original question, first write a predicate type specialized for std::integral_constant :
// Non integral_constant are not kept
template<class T>
struct four_or_more : std::integral_constant<bool, false> {};
// integral_const types are kept if their value is >=4
template<class T, T V>
struct four_or_more<std::integral_constant<T, V>> :
std::integral_constant<bool, V >= 4> {};
And here is a demonstration :
#include <iostream>
int main()
{
auto a = std::make_tuple(
std::integral_constant<int,1> {},
std::integral_constant<int,3> {},
std::integral_constant<int,4> {},
std::integral_constant<int,5> {}
);
using b_type = filter<four_or_more, decltype(a)>::type;
std::cout << "size : " << std::tuple_size<b_type>() << std::endl;
std::cout << std::tuple_element_t<0, b_type>::value << std::endl;
std::cout << std::tuple_element_t<1, b_type>::value << std::endl;
}
You can do that with new STL utilities from C++17. That would be something like that:
template<typename T>
auto filter(T tup) {
return std::apply([&](auto first, auto... rest) {
auto filtered_rest = [&]{
if constexpr (sizeof...(rest)) {
return filter(std::tuple{rest...});
} else {
return std::tuple{};
}
}();
if constexpr (first > 3) {
return std::tuple_cat(std::tuple{first}, filtered_rest);
} else {
return filtered_rest;
}
}, tup);
}
Of course, there is many other ways to do it. In this case I used std::apply and recursion. I start by an empty tuple and I add one element at a time.
Live example: https://godbolt.org/z/qo63r4

Make QtConcurrent::mapped work with lambdas

I am trying to use QtConcurrent::mapped into a QVector<QString>. I already tried a lot of methods, but it seems there are always problems with overloading.
QVector<QString> words = {"one", "two", "three", "four"};
using StrDouble = std::pair<QString, double>;
QFuture<StrDouble> result = QtConcurrent::mapped<StrDouble>(words, [](const QString& word) -> StrDouble {
return std::make_pair(word + word, 10);
});
This snippet returns the following error:
/home/lhahn/dev/cpp/TestLambdaConcurrent/mainwindow.cpp:23: error: no matching function for call to ‘mapped(QVector<QString>&, MainWindow::MainWindow(QWidget*)::<lambda(const QString&)>)’
});
^
I saw this post, which says that Qt cannot find the return value of the lambda, so you have to use std::bind with it. If I try this:
using StrDouble = std::pair<QString, double>;
using std::placeholders::_1;
auto map_fn = [](const QString& word) -> StrDouble {
return std::make_pair(word + word, 10.0);
};
auto wrapper_map_fn = std::bind(map_fn, _1);
QFuture<StrDouble> result = QtConcurrent::mapped<StrDouble>(words, wrapper_map_fn);
But the the error is still similar:
/home/lhahn/dev/cpp/TestLambdaConcurrent/mainwindow.cpp:28: error: no matching function for call to ‘mapped(QVector<QString>&, std::_Bind<MainWindow::MainWindow(QWidget*)::<lambda(const QString&)>(std::_Placeholder<1>)>&)’
QFuture<StrDouble> result = QtConcurrent::mapped<StrDouble>(words, wrapper_map_fn);
^
I also tried wrapping the lambda inside std::function but unfortunately similar results.
Note that this example is just for reproduction, I need a lambda because I am also capturing variables in my code.
The following compiles for me:
QVector<QString> words = {"one", "two", "three", "four"};
std::function<StrDouble(const QString& word)> func = [](const QString &word) {
return std::make_pair(word + word, 10.0);
};
QFuture<StrDouble> result = QtConcurrent::mapped(words, func);
Output of qDebug() << result.results():
(std::pair("oneone",10), std::pair("twotwo",10), std::pair("threethree",10), std::pair("fourfour",10))
Unfortunately that QtConcurrent::mapped does not support lambda function with captures. You could need a custom implementation. For example, you may make a one with AsyncFuture:
template <typename T, typename Sequence, typename Functor>
QFuture<T> mapped(Sequence input, Functor func){
auto defer = AsyncFuture::deferred<T>();
QList<QFuture<T>> futures;
auto combinator = AsyncFuture::combine();
for (int i = 0 ; i < input.size() ; i++) {
auto future = QtConcurrent::run(func, input[i]);
combinator << future;
futures << future;
}
AsyncFuture::observe(combinator.future()).subscribe([=]() {
QList<T> res;
for (int i = 0 ; i < futures.size(); i++) {
res << futures[i].result();
}
auto d = defer;
d.complete(res);
});
return defer.future();
}
Usage:
auto future = mapped<int>(input, func);
Complete Example:
https://github.com/benlau/asyncfuture/blob/master/tests/asyncfutureunittests/example.cpp#L326
QtConcurrent::map[ped] works with functor types that have the result_type member type. Thus you need to wrap the lambda in a class that provides such type. The std::function wrapper provides this, but it might have more overhead - thus we can make our own.
Taking code from How to extract lambda's Return Type and Variadic Parameters Pack back from general template<typename T>, we have:
#include <utility>
#include <type_traits>
template <class T> struct function_traits : function_traits<decltype(&T::operator())> {};
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const> {
// specialization for pointers to member function
using functor_type = ClassType;
using result_type = ReturnType;
using arg_tuple = std::tuple<Args...>;
static constexpr auto arity = sizeof...(Args);
};
template <class Callable, class... Args>
struct CallableWrapper : Callable, function_traits<Callable> {
CallableWrapper(const Callable &f) : Callable(f) {}
CallableWrapper(Callable &&f) : Callable(std::move(f)) {}
};
template <class F, std::size_t ... Is, class T>
auto wrap_impl(F &&f, std::index_sequence<Is...>, T) {
return CallableWrapper<F, typename T::result_type,
std::tuple_element_t<Is, typename T::arg_tuple>...>(std::forward<F>(f));
}
template <class F> auto wrap(F &&f) {
using traits = function_traits<F>;
return wrap_impl(std::forward<F>(f),
std::make_index_sequence<traits::arity>{}, traits{});
}
The wrapped functor, in addition to the result_type needed by Qt, also has the functor_type, arg_tuple, and arity.
Instead of passing the lambda directly, pass the wrapped functor:
auto result = QtConcurrent::mapped<StrDouble>(words, wrap([](const QString& word){
return std::make_pair(word + word, 10);
}));
The value returned by wrap is a functor that implements result_type.

How to get the i-th element from an std::tuple when i isn't know at compile-time?

I have a variable i of type std::size_t and a tuple of type std::tuple. I want to get the i-th element of the tuple. I tried this:
// bindings... is of type const T&...
auto bindings_tuple = std::make_tuple(bindings...);
auto binding = std::tuple_element<i, const T&...>(bindings_tuple);
But I get this compile error saying that the first template argument must be an integral constant expression:
error: non-type template argument of type 'std::size_t' (aka 'unsigned long') is not an integral constant expression
Is it possible to get the i-th element of a tuple, and how to do that?
I would like to do this without using boost, if possible.
This is possible:
struct Functor
{
template<typename T>
void operator()(T& t) const { std::cout << t << std::endl; }
};
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
for_index(int, std::tuple<Tp...> &, FuncT)
{ }
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
for_index(int index, std::tuple<Tp...>& t, FuncT f)
{
if (index == 0) f(std::get<I>(t));
for_index<I + 1, FuncT, Tp...>(index-1, t, f);
}
auto t = make_tuple(1, 2, "abc", "def", 4.0f);
int i = 2; // for example
for_index(i, t, Functor());
This code will print:
abc
Working sample on ideone: sample
You cannot. That's not what a tuple is for. If you need dynamic access to an element, use std::array<T,N>, which is almost identical to std::tuple<T,...,T> but gives you the dynamic [i]-operator; or even a fully dynamic container like std::vector<T>.
This is probably not what OP wants, but anyway, it is possible to return the i-th element using a run-time i provided you return a variant type such as boost::variant or boost::any,
#include <tuple>
#include <stdexcept>
#include <boost/variant.hpp>
template <size_t n, typename... T>
boost::variant<T...> dynamic_get_impl(size_t i, const std::tuple<T...>& tpl)
{
if (i == n)
return std::get<n>(tpl);
else if (n == sizeof...(T) - 1)
throw std::out_of_range("Tuple element out of range.");
else
return dynamic_get_impl<(n < sizeof...(T)-1 ? n+1 : 0)>(i, tpl);
}
template <typename... T>
boost::variant<T...> dynamic_get(size_t i, const std::tuple<T...>& tpl)
{
return dynamic_get_impl<0>(i, tpl);
}
For example:
#include <string>
#include <iostream>
int main()
{
std::tuple<int, float, std::string, int> tpl {4, 6.6, "hello", 7};
for (size_t i = 0; i < 5; ++ i)
std::cout << i << " = " << dynamic_get(i, tpl) << std::endl;
return 0;
}
will print:
0 = 4
1 = 6.6
2 = hello
3 = 7
terminate called after throwing an instance of 'std::out_of_range'
what(): Tuple element out of range.
Aborted
(The boost::variant<T...> requires g++ 4.7)
The question here, what would be the type return type if that would be possible? It has to be known at compile time, but tuple may contain elements of different types.
Let's assume we have a tuple of three elements:
auto tuple = std::make_tuple(10, "", A());
using tuple_type = decltype(tuple);
Apparently, getting N-th element doesn't make much sense. What type would it be? It's not known until runtime. However, rather than getting N-th element you can apply a function to it, given that all elements support some common protocol:
void process(int n)
{
if (n == 0)
func(std::get<0>(tuple));
else if (n == 1)
func(std::get<1>(tuple));
else if (n == 2)
func(std::get<2>(tuple));
}
This code "dynamically" processes element, given the index n. The common protocol in this example is function func which can do something meaningful with all possible types used in the tuple.
However, writing such code by hand is tedious, we want to make it more generic. Let's start with extracting the application function, so we can reuse same process function for different functors:
template<template<typename > class F>
void process(int n)
{
if (n == 0)
{
using E = typename std::tuple_element<0, tuple_type>::type;
F<E>::apply(std::get<0>(tuple));
}
else if (n == 1)
{
using E = typename std::tuple_element<1, tuple_type>::type;
F<E>::apply(std::get<1>(tuple));
}
else if (n == 2)
{
using E = typename std::tuple_element<2, tuple_type>::type;
F<E>::apply(std::get<2>(tuple));
}
}
In this case F could be implemented as something like:
// Prints any printable type to the stdout
struct printer
{
static void apply(E e)
{
std::cout << e << std::endl;
}
}
Let's make compiler to generate all of that code, let's make it generic:
constexpr static std::size_t arity = std::tuple_size<tuple_type>::value;
template<int N>
struct wrapper
{
template<template<typename, typename ... > class F>
static void apply_to(tuple_type& tuple, int idx)
{
if (idx)
// Double recursion: compile and runtime.
// Compile-time "recursion" will be terminated once
// we reach condition N == tuple arity
// Runtime recursion terminates once idx is zero.
wrapper<N + 1>::template apply_to<F>(tuple, idx - 1);
else
{
// idx == 0 (which means original index is equal to N).
using E = typename std::tuple_element<N, tuple_type>::type;
F<E>::apply(std::get<N>(tuple));
}
}
};
// Termination condition: N == arity.
template<>
struct wrapper<arity>
{
template<template<typename, typename ... > class F>
static void apply_to(tuple_type&, int)
{
// Throw exception or something. Index is too big.
}
};
Usage:
wrapper<0>::template apply_to<printer>(tuple, 2);
Making it completely generic is another story, though. At least it needs to be independent of the tuple type. Then, you probably want to generify return type of the functor, so you can return meaningful result. Third, making functor to accept extra parameters.
P.S. I am not real C++ developer, so the approach above could be total nonsence. However, I found it useful for my microcontroller project where I want as much as possible to be resolved at compile time and yet be generic enough, so I can shuffle things around easily. For example, a "menu" in my project is basically a tuple of "actions", there each action is a separate class which supports simple protocol like "print your label at current position on LCD" and "activate and run your UI loop".