Search code examples
c++templatesvariadic-templates

Create an std::variant from the types of an std::tuple using std::tuple_element


I am really struggling with this variadic templates problem (I hope my problem statement is clear, otherwise, make me know if I’m not).

I have a struct defined as this:

// Args are types (all the time)
template<typename T, typename ...Args>
struct my_struct;

Then we declare two separate classes that have a member variable each, of my_struct

struct A { my_struct<A, int, int, char> _a_var; };
struct B { my_struct<B, int, int, char> _b_bar; };
// This way of instatiate my_struct must be as above, T => is the object that owns 
// my_struct variable and the parameter pack must be the same. 
// -This is relevan for the problem, I guess, :) -

And then we have another struct that defines a tuple

template<typename ...Types>
struct params
{
   using tuple_params = typename std::tuple<Types...>;
}

And finally where the problem lies The third structure takes as first parameter an instance of params and another parameter pack And there is a method that will take objects that have an instance of my_struct and must_be_params must have the same types as that instance of my_struct

template<typename must_be_params, typename ...objects>
struct my_problem{

}; 

// Create a my_project instance
void some_random_function() {
   A _a;
   B _b;
   
   // Note that params<> have the same types as the instances of my_struct 
   // in A and B
   my_problem<params<int, int, char>, A, B> _my;
} 

Detailing more of the definition of my_struct, now we have a new member function as this:

template<typename must_be_params, typename ...objects>
struct my_problem{
   template<typename R, typename ...types>
   void some_random_method( my_struct<R, types...> *ptr) {

   }
}; 

THE ACTUAL PROBLEM (long story :) )
I want to have a vector of variants that can hold all the pointers of some_random_method in this way:

struct my_problem{
   template<typename R, typename ...types>
   void some_random_method( my_struct<R, types...> *ptr) {
      _vector.emplace_back(ptr);
   }
private:
   // ASSUMING OBVIOUSLY THAT my_problem was declare as in some_random_function
   // my_problem<params<int, int, char>, A, B> _my;
   std::vector<std::variant< my_struct<A, int, int, char> *, my_struct<B, int, int, char> *> _vector;
}; 

WHERE DOES THE PROBLEM LIES (And this is killing me because I can not get my head around the problem) Notice that the variant have the parameters (int, int, char), now, Where are those types defined within my_problem? well in my partial resolution, they reside in the tuple declared in params<>

So what I want and I just can resolve the issue is to unroll the parameters from the tuple and just insert them into the my_struct that lives inside the std::variant. As far as I know, I’m sure that it can be solved with std::tuple_element, std::index_sequence and some recursive templates black magic.

So far I have manage to do this (which I don’t really know if its the right approach):

template <typename... Types>
struct tuple_to_variant_impl {
    using type = std::variant<Types...>;
};
template <typename... Types>
struct tuple_to_variant_impl<std::tuple<Types...>> {
    using type = typename tuple_to_variant_impl<Types...>::type;
};

template <typename... Types>
using tuple_to_variant = typename tuple_to_variant_impl<Types...>::type;

template<typename must_be_params, typename ...objects>
struct my_problem{
   template<typename R, typename ...types>
   void some_random_method( my_struct<R, types...> *ptr) {

   }

private:
   template<typename ...l>
   using type = typename std::tuple<my_problem<objects, l...>*...>;


   std::vector<tuple_to_variant< type< must_be_params::tuple_params > > > _vector;
  // must_be_params::tuple_params => is equal to std::tuple<int, int, char> 
}; 
struct A { my_struct<A, int, int, char> _a_var; };
struct B { my_struct<B, int, int, char> _b_bar; };
void some_random_function() {
   A _a;
   B _b;
   my_problem<params<int, int, char>, A, B> _my;
} 

According to cppinsights (https://cppinsights.io)

this code expands to:

template<>
struct my_problem<signal_params<int, int, char>, A, B>
{
  // ...
  private: 
  template<typename ... l>
  using type = std::tuple<my_struct<A, l...> *, my_struct<B, l...> *>;
  std::vector<std::variant<my_struct<A, std::tuple<int, int, char> > *, my_struct<B, std::tuple<int, int, char> > *>, std::allocator<std::variant<my_struct<A, std::tuple<int, int, char> > *, my_struct<B, std::tuple<int, int, char> > *> > > _vector;
  // ...
};

As you can see so far this technic expands to something like this

std::vector<
    std::variant<
       my_struct<A, std::tuple<int, int, char> > *, 
       my_struct<B, std::tuple<int, int, char> > *
       > 
   > _vector;

BUT what I really want is to unroll the tuple types o create something like this

std::vector<
    std::variant<
       my_struct<A, int, int, char > *, // NOTICE: NO MORE std::tuple
       my_struct<B, int, int, char > *  // NOTICE: NO MORE std::tuple
       > 
   > _vector;

How can it be done?


Solution

  • Partial specialization might be simpler:

    template <typename must_be_params, typename... objects>
    struct my_problem;
    
    template <typename... Ts, typename... objects>
    struct my_problem<params<Ts...>, objects...>
    {
        std::vector<std::variant<my_struct<objects, Ts...>*...> _vector;
    };
    

    Demo