c++templatesinheritancetype-traits

Get base classes as tuple


This is follow-up to Wrap each base class in a template class .

Playing around, I figured out a way to convert a tuple of distinct types to a struct inheriting from each of them:

#include <cstdio>
#include <tuple>
#include <type_traits>

namespace detail {

template <typename... Ts>
struct tuple_to_struct_impl : Ts... {};

}

template <typename>
struct tuple_to_struct;

template <typename... Ts>
struct tuple_to_struct<std::tuple<Ts...>> :
    detail::tuple_to_struct_impl<Ts...> {};

struct x { int i; };
struct y { int j; };
struct z { int k; };

using t = std::tuple<x, y, z>;
using xyz = tuple_to_struct<t>;

int main() {
    xyz s{ 1, 2, 3 };
    std::printf("%d, %d, %d", s.i, s.j, s.k);
}

Run it on GodBolt

Is there a way to reverse this, i.e. implement struct_to_tuple? std::is_base_of requires to know both types up front, so I would have to run it with a list of all possible candidates, e.g. struct_to_tuple<xyz, x, y, z, std::string...>.

Get base class for a type in class hierarchy touches on the topic, but looks out of date with some speculation on C++14. It also lists multiple inheritance as a problem, while it is exactly what I want.


Solution

  • The answer you link is still correct. There are more related questions that suggest std::bases type trait was proposed but rejected (see eg Status of std::bases and std::direct_bases). However, gcc supports it as tr2:

    A rather simplistic demo:

    #include <tr2/type_traits>
    #include <type_traits>
    
    struct foo {};
    struct bar {};
    struct derived : foo,bar {};
    struct derived2 : foo,bar {};
    int main() {
        using x = std::tr2::direct_bases<derived>::type;
        using y = std::tr2::direct_bases<derived2>::type;
        static_assert(std::is_same<x,y>::value);
        static_assert(std::is_same<x,std::tr2::__reflection_typelist<foo,bar>>::value);
    }
    

    Live Demo

    For a portable way you will have to wait until proper reflection is in.

    Combining Jarod42's version from a comment with your trait, we can see that it actually works, by constructing xyz from the tuple, get its bases, put them in a tuple, turn that tuple again to a struct with base classes and see that it is the same type as the original xyz:

    #include <tr2/type_traits>
    #include <type_traits>
    #include <cstdio>
    #include <tuple>
    #include <type_traits>
    
    template <typename> struct tuple_to_struct;
    template <typename... Ts> struct tuple_to_struct<std::tuple<Ts...>> : Ts... {};
    
    template <typename> struct typelist_to_tuple;
    template <typename... Ts> struct typelist_to_tuple<std::tr2::__reflection_typelist<Ts...>> {
        using type = std::tuple<Ts...>;
    };
    template <typename T> using typelist_to_tuple_t = typename typelist_to_tuple<T>::type;
    
    struct x { int i; };
    struct y { int j; };
    struct z { int k; };
    
    using t = std::tuple<x, y, z>;
    using xyz = tuple_to_struct<t>;
     
    int main() {
        xyz s{ 1, 2, 3 };
        std::printf("%d, %d, %d", s.i, s.j, s.k);
    
        using bases = std::tr2::direct_bases<xyz>::type;
        using base_tuple = typelist_to_tuple_t<bases>;
        using abc = tuple_to_struct<base_tuple>;
        static_assert(std::is_same_v<abc,xyz>);
    }
    

    Live Demo