Type trait that gets the list of inner struct types of a given struct

51 Views Asked by At

Consider the following code:

#include <tuple>
#include <type_traits>

struct foo
{
    struct a {};
    struct b {};
    struct c {};
};

// we use here an hypothetical type trait 'get_struct_list_t'
using T = get_struct_list_t<foo>;

static_assert (std::is_same_v <T, std::tuple<foo::a, foo::b, foo::c>>);

int main () {}

Question: is is possible to design a type trait get_struct_list_t that returns the list of inner struct types of a given struct ?

I don't think it is possible in c++ since it would require to have some reflection feature, so my question is mainly to confirm this point.

1

There are 1 best solutions below

0
edrezen On

The original question didn't make any assumption on the inner structs whose types list has to be retrieved.

If these inner structs only hold type information (ie. no data members), we could use the following trick: instead of naming for instance struct a {};, we could instead define a member of an anonymous struct, ie. struct {} a;. The good point now is that we have something to get a type information from, in other words we can use some struct_to_tuple machinery to obtain what we want. However, this solution is quite akward and misleading for the end user. It's more a matter of fun than for every day usage...

Here is the whole example. Note that I use here the following gist for getting a std::tuple from a structure.

#include <type_traits>
#include <tuple>

///////////////////////////////////////////////////////////////////////////////
// https://gist.github.com/utilForever/1a058050b8af3ef46b58bcfa01d5375d
///////////////////////////////////////////////////////////////////////////////

template<class T, typename... Args>  decltype(void(T{std::declval<Args>()...}), std::true_type())  test (int);
template<class T, typename... Args>  std::false_type                                               test (...);
template<class T, typename... Args> struct is_braces_constructible : decltype(test<T, Args...>(0))  {};

struct any_type { template<class T> constexpr operator T(); };

template<class T>
auto to_tuple(T&& object) noexcept
{
    using type = std::decay_t<T>;
    if constexpr(is_braces_constructible<type, any_type, any_type, any_type, any_type>{}) {
      auto&& [p1, p2, p3, p4] = object;
      return std::make_tuple(p1, p2, p3, p4);
    } else if constexpr(is_braces_constructible<type, any_type, any_type, any_type>{}) {
      auto&& [p1, p2, p3] = object;
      return std::make_tuple(p1, p2, p3);
    } else if constexpr(is_braces_constructible<type, any_type, any_type>{}) {
      auto&& [p1, p2] = object;
      return std::make_tuple(p1, p2);
    } else if constexpr(is_braces_constructible<type, any_type>{}) {
      auto&& [p1] = object;
      return std::make_tuple(p1);
    } else {
        return std::make_tuple();
    }
}

///////////////////////////////////////////////////////////////////////////////
// The definition of our trait
///////////////////////////////////////////////////////////////////////////////
template<typename T>
struct get_struct_list 
{
    using type = decltype (to_tuple (T{}));
};

template<typename T>
using get_struct_list_t = typename get_struct_list<T>::type;

////////////////////////////////////////////////////////////
// A testing struct
////////////////////////////////////////////////////////////
struct foo
{
    // the 'inversion' trick is here 
    // -> use members of anonymous struct instead of named structs

    struct { using type=int;   } a;   // instead of struct a { using type=int; };
    struct { using type=char;  } b;
    struct { using type=float; } c;
};

using T = get_struct_list_t<foo>;

static_assert (std::is_same_v <std::tuple_element_t<0,T>::type, int  >);
static_assert (std::is_same_v <std::tuple_element_t<1,T>::type, char >);
static_assert (std::is_same_v <std::tuple_element_t<2,T>::type, float>);

int main ()  {}