Template Tuple - Calling a Function on Each Element

Template tuple - calling a function on each element

You can quite easily do that with some indices machinery. Given a meta-function gen_seq for generating compile-time integer sequences (encapsulated by the seq class template):

namespace detail
{
template<int... Is>
struct seq { };

template<int N, int... Is>
struct gen_seq : gen_seq<N - 1, N - 1, Is...> { };

template<int... Is>
struct gen_seq<0, Is...> : seq<Is...> { };
}

And the following function templates:

#include <tuple>

namespace detail
{
template<typename T, typename F, int... Is>
void for_each(T&& t, F f, seq<Is...>)
{
auto l = { (f(std::get<Is>(t)), 0)... };
}
}

template<typename... Ts, typename F>
void for_each_in_tuple(std::tuple<Ts...> const& t, F f)
{
detail::for_each(t, f, detail::gen_seq<sizeof...(Ts)>());
}

You can use the for_each_in_tuple function above this way:

#include <string>
#include <iostream>

struct my_functor
{
template<typename T>
void operator () (T&& t)
{
std::cout << t << std::endl;
}
};

int main()
{
std::tuple<int, double, std::string> t(42, 3.14, "Hello World!");
for_each_in_tuple(t, my_functor());
}

Here is a live example.

In your concrete situation, this is how you could use it:

template<typename... Ts>
struct TupleOfVectors
{
std::tuple<std::vector<Ts>...> t;

void do_something_to_each_vec()
{
for_each_in_tuple(t, tuple_vector_functor());
}

struct tuple_vector_functor
{
template<typename T>
void operator () (T const &v)
{
// Do something on the argument vector...
}
};
};

And once again, here is a live example.

Update

If you're using C++14 or later, you can replace the seq and gen_seq classes above with std::integer_sequence like so:

namespace detail
{
template<typename T, typename F, int... Is>
void
for_each(T&& t, F f, std::integer_sequence<int, Is...>)
{
auto l = { (f(std::get<Is>(t)), 0)... };
}
} // namespace detail

template<typename... Ts, typename F>
void
for_each_in_tuple(std::tuple<Ts...> const& t, F f)
{
detail::for_each(t, f, std::make_integer_sequence<int, sizeof...(Ts)>());
}

If you're using C++17 or later you can do this (from this comment below):

std::apply([](auto ...x){std::make_tuple(some_function(x)...);} , the_tuple);

c++ applying templated function to each element of a tuple

You need to unpack the tuple into apply like this:

void display()
{
std::apply([](auto ...ts) { (..., print(ts)); },values_);
}

Here's a demo.

Note that this solution uses a fold-expression to make the syntax easier.

Calling a common method of tuple elements

There may be snazzier C++17 ways of doing it, but there is always good old-fashioned partially-specialized recursion. We'll make a struct that represents your recursive algorithm, and then we'll build a function wrapper around that struct to aid in type inference. First, we'll need some imports.

#include <tuple>
#include <utility>
#include <iostream> // Just for debugging later :)

Here's our structure definition.

template <typename Input, typename... Ts>
struct ApplyOp;

Not very interesting. It's an incomplete type, but we're going to provide specializations. As with any recursion, we need a base case and a recursive step. We're inducting on the tuple elements (you're right to think of this as a fold-like operation), so our base case is when the tuple is empty.

template <typename Input>
struct ApplyOp<Input> {
Input apply(Input x) {
return x;
}
};

In this case, we just return x. Computation complete.

Now our recursive step takes a variable number of arguments (at least one) and invokes .apply.

template <typename Input, typename T, typename... Ts>
struct ApplyOp<Input, T, Ts...> {
auto apply(Input x, const T& first, const Ts&... rest) {
auto tail_op = ApplyOp<Input, Ts...>();
return first.apply(tail_op.apply(x, rest...));
}
};

The tail_op is our recursive call. It instantiates the next version of ApplyOp. There are two apply calls in this code. first.apply is the apply method in the type T; this is the method you control which determines what happens at each step. The tail_op.apply is our recursive call to either another version of this apply function or to the base case, depending on what Ts... is.

Note that we haven't said anything about tuples yet. We've just taken a variadic parameter pack. We're going to convert the tuple into a parameter pack using an std::integer_sequence (More specifically, an std::index_sequence). Basically, we want to take a tuple containing N elements and convert it to a sequence of parameters of the form

std::get<0>(tup), std::get<1>(tup), ..., std::get<N-1>(tup)

So we need to get an index sequence from 0 up to N-1 inclusive (where N-1 is our std::tuple_size).

template <typename Input, typename... Ts>
auto apply(const std::tuple<Ts...>& tpl, Input x) {
using seq = std::make_index_sequence<std::tuple_size<std::tuple<Ts...>>::value>;
// ???
}

That complicated-looking type alias is building our index sequence. We take the tuple's size (std::tuple_size<std::tuple<Ts...>>::value) and pass it to std::make_index_sequence, which gives us an std::index_sequence<0, 1, 2, ..., N-1>. Now we need to get that index sequence as a parameter pack. We can do that with one extra layer of indirection to get type inference.

template <typename Input, typename... Ts, std::size_t... Is>
auto apply(const std::tuple<Ts...>& tpl, Input x, std::index_sequence<Is...>) {
auto op = ApplyOp<Input, Ts...>();
return op.apply(x, std::get<Is>(tpl)...);
}

template <typename Input, typename... Ts>
auto apply(const std::tuple<Ts...>& tpl, Input x) {
using seq = std::make_index_sequence<std::tuple_size<std::tuple<Ts...>>::value>;
return apply(tpl, x, seq());
}

The second apply is the one outside users call. They pass a tuple and an input value. Then we construct an std::index_sequence of the appropriate type and pass that to the first apply, which uses that index sequence to access each element of the tuple in turn.

Complete, runnable example

Call member function for each element of std::tuple

This is a fold expression that does

(std::get<Is>(tpl).hi())

for every Is that is packed inside.

How to call a templated function for each type in a tuple (acting as type list) with tuple b as argument

Use template partial specialization to extract the type of typelist, then use fold-expression to invoke doSomething with different template parameters

template<typename Tuple>
struct someFunctor;

template<typename... Args>
struct someFunctor<std::tuple<Args...>> {
template<class T>
constexpr void operator()(T&& x) {
(doSomething<Args>(std::forward<T>(x)), ...);
}
};

Demo

using types = std::tuple<int, char, float, double, w<int>, w<float>>;
constexpr auto data = std::make_tuple(1, 2, 3.0, 4.0f, w(5.0f));
someFunctor<types>()(data);

Generically call member function on each element of a tuple

You can't use mem_fn to create a wrapper that calls a member function on objects of heterogeneous type, as the wrapper created by mem_fn wraps a pointer to a particular member of a particular type.

The trick is to pass something with a templated function call operator that can accept any appropriate type and call the member function.

In C++14, pass a polymorphic lambda:

[](auto&& obj) -> decltype(auto) { return std::forward<decltype(obj)>(obj).get(); }

For C++11, you'd need to write the equivalent functor by hand:

struct call_get {
template<class T>
auto operator()(T&& obj) const -> decltype(std::forward<T>(obj).get()) {
return std::forward<T>(obj).get();
}
};

Call function for each tuple element on one object without recursion

You may use std::index_sequence<Is...>, something like:

namespace detail
{

template <std::size_t...Is, typename T>
void a_call(A& a, std::index_sequence<Is...>, const T& t)
{
int dummy[] = {0, ((a = a.call(std::get<Is>(t))), void(), 0)...};
static_cast<void>(dummy); // Avoid warning for unused variable.
}

}

template <typename ... Ts>
void a_call(A& a, const std::tuple<Ts...>& t)
{
detail::a_call(a, std::index_sequence_for<Ts...>{}, t);
}

In C++17, Folding expression allows:

    template <std::size_t...Is, typename T>
void a_call(A& a, std::index_sequence<Is...>, const T& t)
{
(static_cast<void>(a = a.call(std::get<Is>(t))), ...);
}

or even, with std::apply:

template <typename ... Ts>
void a_call(A& a, const std::tuple<Ts...>& t)
{
std::apply([&](const auto&... args){ (static_cast<void>(a = a.call(args)), ...); }, t);
}

Apply function to each element in tuple, cast each to a different type in a type pack, then pass as parameter pack

The main problem is that your tuple contains std::shared_ptr<WithState<StateTypes>>... so that's what apply will try to call your given lambda with, but the lambda only takes StateTypes&&....

There are a few more changes to make it work, but first off the working thing:

https://godbolt.org/z/hyFBtV

  • I changed getStateFor to be a template function where you specify the type you expect:

    template<class StateType>
    StateType getStateFor(std::shared_ptr<Subject> s)
    {
    if (auto withState = std::dynamic_pointer_cast<WithState<StateType>>(s))
    return withState->getValue();
    throw "AHHH";
    }

    You can still assign that to a std::any if you want (or provide a non-templated overload that returns std::any). But for the purposes here casting to std::any and back is just unnecessary overhead - if anything, it's less type-safe.

  • Your lambda arguments were StateTypes&&... withStates. Aside from the type itself needing to be different, the && doesn't work unless you actually provide temporaries to the lambda (which std::apply won't do), or if you do type deduction via auto&& (which is different). In the code the lambda expression is thus (I took by value for simplicity, you might take by [const] reference):

        [this](std::shared_ptr<WithState<StateTypes>>... withStatePtrs) {
    handleStates(getStateFor<StateTypes>(withStatePtrs)...);
    }
  • You also don't need a dynamic_pointer_cast to go from std::shared_ptr<WithState<T>> to std::shared_ptr<Subject>.

  • Your tuple type had an extra >.


Edit: Using the snippet in the updated question, you get this:

https://godbolt.org/z/D_AJ1n

The same considerations still apply, you just have to put an any_cast in there:

void handleStatesForSubjects(std::function<std::any(std::shared_ptr<Subject>)> getStateFor)
{
std::apply(
[this, getStateFor](std::shared_ptr<WithState<StateTypes>>... withStates) {
handleStates(std::any_cast<StateTypes>(getStateFor(withStates))...);
},
subjects
);
}

It's probably more straightforward than you expected, but you just write down the operation for each element: Call getStateFor, then any_cast it. Repeat for each variadic argument (...). You don't need any dynamic_pointer_cast or similar - std::shared_ptr<Derived> is implicitly convertible to std::shared_ptr<Base>.

Applying a function to each element of a tuple

Another way:

namespace details {

struct apply_unary_helper_t {};

template<class T>
T&& operator,(T&& t, apply_unary_helper_t) { // Keep the non-void result.
return std::forward<T>(t);
}

template <typename Ftor, typename Tuple, size_t... Is>
void apply_unary(Ftor&& ftor, Tuple&& tuple, std::index_sequence<Is...>) {
auto r = {(ftor(std::get<Is>(std::forward<Tuple>(tuple))), apply_unary_helper_t{})...};
static_cast<void>(r); // Suppress unused variable warning.
}

} // namespace details

template <typename Ftor, typename Tuple>
void apply_unary(Ftor&& ftor, Tuple&& tuple) {
details::apply_unary(std::forward<Ftor>(ftor),
std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size<std::remove_reference_t<Tuple>>::value> {});
}

In the above, it applies operator, to the result of ftor and apply_unary_helper_t. If the result of ftor is void, then r is std::initializer_list<details::apply_unary_helper_t>, otherwise r is std::initializer_list<decltype(ftor(...))> which you can make use of.

Create a template function that returns different tuple types depending on template argument types conditions

C++17 solution, C++11 if you replace _v usings with ::value.
Does not require distinct bases, but it will always replace each default base in the default tuple by the left-most matching type in the passed tuple argument.

#include <tuple>
#include <type_traits>

// Returns the first type from Ts that is derived from Base. Returns Base if there is not one.
template<typename Base,typename...Ts>
struct pick_derived;

// No Ts were derived from Base.
template<typename Base>
struct pick_derived<Base>{
using type=Base;
};

template<typename Base,typename Derived, typename...Tail>
struct pick_derived<Base,Derived,Tail...>{
using type = typename std::conditional<std::is_base_of_v<Base,Derived>,
Derived,// Return it. Otherwise continue searching.
typename pick_derived<Base,Tail...>::type>::type;
};

template<typename SourceTuple, typename DefaultTuple>
struct tup_transformer_impl;

template<typename...Ts, typename...Ds>
struct tup_transformer_impl<std::tuple<Ts...>,std::tuple<Ds...>>{
// Fancy double pack expansion
// For each default Ds type, try to replace it with a derived type from Ts.
using type = std::tuple<typename pick_derived<Ds,Ts...>::type...>;
};

#include <iostream>

struct B0 { };
struct B1 { };

// Tweak this.
using default_tuple = std::tuple<B0,B1>;
template<typename Tuple>
using tup_transform = typename tup_transformer_impl<Tuple,default_tuple>::type;

template<class Tuple>
tup_transform<Tuple> make_complete_tuple()
{
return {};
}

struct A : public B0 { };
struct C : public B1 { };


int main()
{
auto complete_tuple0 = make_complete_tuple<std::tuple<A>>();
// complete_tuple0 shoud be std::tuple<A, B1>;
static_assert(std::is_same_v<decltype(complete_tuple0),std::tuple<A, B1>>);

auto complete_tuple1 = make_complete_tuple<std::tuple<C>>();
// complete_tuple1 shoud be std::tuple<B0, C>;
static_assert(std::is_same_v<decltype(complete_tuple1),std::tuple<B0, C>>);

std::cin.get();
return 0;
}

I guess you can just call it like:

tup_transform<std::tuple<A>> complete_tuple0;

( Changing it to tup_transform<A> complete_tuple0; is quite easy. Just change tup_transformer_impl to use Ds... directly instead of unpacking them.)

Of course, all this requires that each type is default constructible. If that is not the case, I think the solution can be adapted to work if the derived types are at least movable. In that case each derived value would have to be passed to make_complete_tuple function.



Related Topics



Leave a reply



Submit