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

2

5

How to call a template function for each type in a typelist with arguments (e.g. another tuple)?

Given is a typelist std::tuple<T1, T2, T3, ...> and a std::tuple containing data.

template <typename T>
void doSomething (const auto& arg) {
    std::cout << __PRETTY_FUNCTION__ << '\n';
}

template <typename T> struct w {T v; w(T _v) : v{_v} {}};

int main () {
    using types = std::tuple<int, char, float, double, w<int>, w<float>>; // used as type list
    constexpr auto data = std::make_tuple(1, 2, 3.0, 4.0f, w(5.0));
    // call doSomething<T>(data) for each type in types
    // like
    // someFunctor<types>(doSomething, data);
}

My current idea is a functor like appreach that receives the typelist to extract nexted types and std::tuple<Ts> having an operator () to call doSomething<T>(args) for each of Ts.

template<template<typename...> typename TL, typename... Ts>
struct someFunctor {
    template<typename... Args>
    static constexpr void operator() (Args&&... args) {
        (doSomething<Ts>(std::forward<Args>(args)...), ...);
    }
};

Not sure if that's the smartest approach. Brain fog blocked me so far to get it work.

Something answered 8/5, 2022 at 15:4 Comment(7)
Why is the tuple of types different from the type of the tuple of values? It even has different length in your example. What should happen if the types of an element don't coincide? In other words, why do you want someFunctor<types>(doSomething, data); instead of someFunctor(doSomething, data);?Bunchy
Is const auto & in doSomething intended, or was it supposed to be const T &?Memnon
@Bunchy types and data are independent of each other. The to be called function will do different things with data, depending on each type of types.Something
@ChrisG. I am still not sure what should happen. Do you want doSomething to be called for each type in types once with arg being a reference to data each time?Bunchy
@Memnon this is intended. It needs each T + the argument(s) like doSomething<int>(data); doSomething<char>(data); doSomething<w<int>>(data); ...Something
@Bunchy yes, indeed! Passing a functor/lambda instead might do it.Something
@ChrisG. Nevermind, for some reason I forgot that you can call operator() explicitly with template arguments.Bunchy
T
5

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);
Toilet answered 8/5, 2022 at 15:19 Comment(2)
That looks pretty nice 康桓瑋! Is there a way to make use of a template template argument to avoid specializations for different type containers than std::tuple? Indeed the type comparison is over the top as @Bunchy mentioned, but handy now that I see it.Something
@ChrisG. template<template<typename...> class TL, typename... Args> struct someFunctor<TL<Args...>> {Polder
P
4

Here's a short piece of code to do it:

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.0));
[&]<template<typename...> class TL, typename... Ts>(TL<Ts...>*) {
    // Now have a parameter pack of `Ts`
    (doSomething<Ts>(data), ...);
}((types*) nullptr); 

And turned into a reusable function, you can do something like:

template<typename types, typename Functor, typename... Args>
constexpr void mapTypeList(Functor&& f, Args&&... args) {
    [&]<template<typename...> class TL, typename... Ts>(TL<Ts...>*) {
        ((void) f.template operator()<Ts>(std::forward<Args>(args)...), ...);
    }(static_cast<types*>(nullptr));
}


// You need to wrap `doSomething` in a lambda
mapTypeList<types>([]<typename T>(const auto& data) { doSomething<T>(data); }, data);
// or maybe capturing data:
mapTypeList<types>([&]<typename T>() { doSomething<T>(data); });

Example usage: https://godbolt.org/z/53x44bGvK

Polder answered 8/5, 2022 at 15:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.