0

I want to iterate over types of a tuple, not its elements.
Imagine you have a generic, base-class interface Controller and you want to have a vector of pointers (I use raw pointer instead of smart, for readability): std::vector<Controller*> ctrls;

Now you want to add many implementations of Controller interface to this vector, so you can do this:

ctrls.emplace_back(new MouseCtrl());
ctrls.emplace_back(new KeyboardCtrl());
ctrls.emplace_back(new ScreenCtrl());
(... and so on)

But this is ugly and not exactly extendable (as in Open-Close Principle), so it would be better to have, for example, a tuple: using Controllers = std::tuple<MouseCtrl, KeyboardCtrl, ScreenCtrl>; and then, in the some init function, iterate over those types:

for (T : Controllers> { 
   ctrls.emplace_back(new T());
}

Obviously, the code above it's not valid C++ syntax. So the question is: how to do this?. I've looked both into std::apply and std::visit / std::variant, but I don't have idea how to do this (they iterate over elements, not types).

zupazt3
  • 966
  • 9
  • 30
  • Types in a tuple can be iterated only at compile time. – πάντα ῥεῖ Oct 05 '20 at 16:25
  • 1
    `std::tuple` is a value type. If you just want to default construct your elements you can just default construct the `std::tuple`. If you need more sophisticated initialization the question arises how that should be done in your case which i am afraid we can'T answere without further information. In general you "iterate" over types with *variadic templates* – Sebastian Hoffmann Oct 05 '20 at 16:25
  • The duplicate question doesn't really answer the question if you cannot construct the tuple. Here's a solution that works purely with the types: https://godbolt.org/z/rYfGoj (which I can't answer on a closed question) – Artyer Oct 05 '20 at 16:47

2 Answers2

2

You can use std::tuple_size and std::tuple_element to get the type of every tuple element (this will put it in reverse order, but with a little modification you can revert the order):

#include <iostream>
#include <variant>
#include <vector>

using namespace std;

using Controllers = tuple<int, char, float>;
using ControllersContainer = vector<variant<int, char, float>>;

template <size_t N>
void add(ControllersContainer& ctrls)
{
    ctrls.emplace_back(tuple_element_t<N-1, Controllers>{});
    add<N - 1>(ctrls);
}

template <>
void add<0>(ControllersContainer& ctrls)
{
    ctrls.emplace_back(tuple_element_t<0, Controllers>{});
}

int main()
{
    ControllersContainer ctrls;
    add<tuple_size_v<Controllers>>(ctrls);
}
StPiere
  • 4,113
  • 15
  • 24
  • Important to note that this will only work at compile-time. Probably not what the OP wants. – Tanveer Badar Oct 06 '20 at 07:22
  • The question was about iterating over a types in a tuple. A tuple is a static type - "iteration" is possible only at compile time. Although iteration here is implemented with recursion. – StPiere Oct 06 '20 at 07:26
  • @StPiere This is exactly was I was looking for. Do you know if there is anything in the standard lib that already provide this kind of functionality - so that I could skip adding "add" function and just provide some Lambda fun with `ctrls.emplace_back(tuple_element_t<0, Controllers>{});` ? – zupazt3 Oct 06 '20 at 12:46
  • @zupazit: I'm not aware of direct method. One other possibility would be to go over make_index_sequence and tuple_size with comma operator. – StPiere Oct 06 '20 at 12:55
0

If C++20 is allowed, there is a more convenient way to solve this:

template<typename T, typename FUNC>
static inline constexpr void for_each_type(FUNC&& func)
{
    auto  __hander = []<typename T, typename FUNC, size_t... I>(FUNC && func, std::index_sequence<I...>) {
        (func.template operator() < std::tuple_element_t<I, T> > (), ...);
    };
    __hander.template operator() < T > (
        std::forward<FUNC>(func),
        std::make_index_sequence<std::tuple_size<T>::value>{}
    );
}

Now you have two classes with same method, and make they a tuple:

class A
{
public:
    static const char* name() { return "A"; };
};

class B
{
public:
    static const char* name() { return "B"; };
};
using AB = std::tuple<A, B>;

And then:

int main(int argc, char* argv[])
{
    for_each_type<AB>(
        []<typename T>()
        {
            std::cout << T::name() << std::endl;
        }
    );

    return 0;
}
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
lux
  • 1