#include <cstdio>
#include <typeinfo>

#include <utility>
#include <type_traits>

namespace luple_ns {

  //type list
  template<typename... TT> struct type_list { static const int size = sizeof...(TT); };

  //get element type by index
  template<typename T, int N, int M = 0> struct tlist_get;

  template<int N, int M, typename T, typename... TT> struct tlist_get< type_list<T, TT...>, N, M > {
    static_assert(N < (int) sizeof...(TT)+1 + M, "type index out of bounds");
    using type = std::conditional_t< N == M, T, typename tlist_get< type_list<TT...>, N, M + 1 >::type >;
  };

  template<int N, int M> struct tlist_get< type_list<>, N, M > {
    using type = void;
  };

  template<typename T, int N>
  using tlist_get_t = typename tlist_get<T, N>::type;

  //get element index by type
  template<typename T, typename U, int N = 0> struct tlist_get_n;

  template<typename U, int N, typename T, typename... TT> struct tlist_get_n< type_list<T, TT...>, U, N > {
    static const int value = std::is_same< T, U >::value ? N : tlist_get_n< type_list<TT...>, U, N + 1 >::value;
  };
  template<typename U, int N> struct tlist_get_n< type_list<>, U, N > {
    static const int value = -1;
  };

  //a building block that is used in multiple inheritane
  template<typename T, int N> struct tuple_element {
    tlist_get_t<T, N> value;
  };

  //base of luple and also parent of tuple_element's
  template<typename T, typename U> struct tuple_base;

  template<typename... TT, int... NN>
  struct tuple_base< type_list<TT...>, std::integer_sequence<int, NN...> > : tuple_element< type_list<TT...>, NN >... {

    using tlist = type_list<TT...>;

    template<typename... UU, typename U = std::enable_if_t< sizeof...(UU) == sizeof...(NN) >>
    constexpr tuple_base ( UU&&... args ) : tuple_element< tlist, NN >{ std::forward<UU>(args) }... {}
    constexpr tuple_base ( TT const&... args ) : tuple_element< tlist, NN >{ args }... {}
    constexpr tuple_base ( TT&&... args ) : tuple_element< tlist, NN >{ std::move(args) }... {}
    constexpr tuple_base () {}
  };

  //tuple=luple
  //T: type_list< ... user types ... >
  template<typename T> struct tuple : tuple_base< T, std::make_integer_sequence<int, T::size> > {

    using type_list = T;
    using base = tuple_base< T, std::make_integer_sequence<int, T::size> >;

    template<int N> constexpr auto& get() {
      static_assert(N < T::size, "tuple::get -> out of bounds access");
      return tuple_element< T, N >::value;
    }

    template<typename U> constexpr auto& get() {
      static_assert(tlist_get_n<T, U>::value != -1, "no such type in type list");
      return tuple_element< T, tlist_get_n<T, U>::value >::value;
    }

    template<int N> constexpr auto& get() const {
      static_assert(N < T::size, "tuple::get -> out of bounds access");
      return tuple_element< T, N >::value;
    }

    template<typename U> constexpr auto& get() const {
      static_assert(tlist_get_n<T, U>::value != -1, "no such type in type list");
      return tuple_element< T, tlist_get_n<T, U>::value >::value;
    }

    using base::base;
  };

  template<int N, typename T> constexpr auto& get ( tuple<T>& t ) { return t.template get<N>(); }
  template<typename U, typename T> constexpr auto& get ( tuple<T>& t ) { return t.template get<U>(); }

  template<int N, typename T> constexpr auto& get ( tuple<T> const& t ) { return t.template get<N>(); }
  template<typename U, typename T> constexpr auto& get ( tuple<T> const& t ) { return t.template get<U>(); }

  template<typename T> constexpr auto size ( tuple<T> const& t ) { return T::size; }
  template<typename U, typename T> constexpr auto index ( tuple<T> const& t ) { return tlist_get_n< T, U >::value; }

  //type for index
  template<typename T, int N>
  using element_t = tlist_get_t< typename T::type_list, N >;

  //helper to make luple<A, B, C> and luple< type_list<A, B, C> > equivalent
  template<typename T> struct luple_impl {
    using type = tuple<T>;
  };

  template<typename... TT> struct luple_impl< type_list< type_list<TT...> > > {
    using type = typename luple_impl< type_list<TT...> >::type;
  };

  //template alias to wrap types into type_list
  template<typename... TT>
  using luple = typename luple_impl< type_list<TT...> >::type;

  //helper to run code for every member of luple
  template<int... N, typename T0, typename T1>
  void luple_do_impl (std::integer_sequence<int, N...>, T0& t, T1 fn) {
    char dummy[]{ (fn( get<N>(t) ), char{})... };
    (void)dummy;
  }

  //helper to run code for every member of tuple
  template<typename T0, typename T1>
  void luple_do (T0& t, T1 fn) {
    luple_do_impl( std::make_integer_sequence< int, T0::type_list::size >{}, t, fn );
  }
  
}

//import into global namespace
using luple_ns::luple;
using luple_ns::get;
using luple_ns::index;



namespace struct_reader {

  using namespace luple_ns;

  //this is the main type list, add your own types here
  using type_list_t = type_list<
            void *, bool, char, unsigned char, signed char, short, int, long, long long, 
            unsigned short, unsigned int, unsigned long, unsigned long long, 
            float, double, long double,
            _IO_marker *,_IO_FILE *
          >;

  //helper to get type using a templated conversion operator
  template<typename T>
  struct read_type {
    template<typename U>
    constexpr operator U() {
      using noptr = std::remove_pointer_t<U>;
      using nocon = std::remove_const_t<noptr>;
      static_assert( tlist_get_n<T, U>::value != -1 || tlist_get_n<T, nocon>::value != -1, "no such type in type list");
      constexpr int const tid = 0xFFFF, is_ptr = 1 << 16, is_con = 1 << 17;
      data = tlist_get_n<T, U>::value;
      if( data == -1 ) {
        data = tlist_get_n<T, nocon>::value & tid;
        data = data | (std::is_pointer<U>::value ? is_ptr : 0);
        data = data | (std::is_const<noptr>::value ? is_con : 0);
      }
      return {};
    }
    int data;
  };

  using read_type_t = read_type< type_list_t >;

  //here we're using overload resolution to get a data member type
  template<typename T, int... N>
  constexpr auto get_type_id(int n) {
    read_type_t tid[sizeof...(N)]{};
    T d = T{ tid[N]... }; (void)d;
    return tid[n].data;
  }

  //helper to rebuild the type
  template<typename T, int tid, int is_pointer, int is_const>
  constexpr auto get_type() {
    using type = tlist_get_t<T, tid>;
    using ctype = std::conditional_t< (bool)is_const, std::add_const_t<type>, type >;
    using ptype = std::conditional_t< (bool)is_pointer, std::add_pointer_t<ctype>, ctype >;
    return ptype{};
  }

  //read struct data member types and put it into a type list
  template<typename T, int... N>
  constexpr auto get_type_list(std::integer_sequence<int, N...>) {
    constexpr int t[] = { get_type_id<T, N...>(N)... };
    constexpr int const tid = 0xFFFF, is_ptr = 1 << 16, is_con = 1 << 17;
    return type_list< decltype(get_type<type_list_t, t[N]&tid, t[N]&is_ptr, t[N]&is_con>())...>{};
  }

  //get fields number using expression SFINAE
  template<typename T, int... N>
  constexpr auto fields_number(...) { return sizeof...(N)-1; }

  template<typename T, int... N>
  constexpr auto fields_number(int) -> decltype(T{ (N,read_type_t{})... }, sizeof(0)) { return fields_number<T, N..., 0>(0); }

  //and here is our hot and fresh out of kitchen type list (alias template)
  template<typename T>
  using as_type_list = decltype(get_type_list< T >(std::make_integer_sequence< int, fields_number<T>(0) >{}));

}

int main() {

  using FILE_tlist = struct_reader::as_type_list< FILE >;

  //for being able to read out FILE I had to add _IO_marker* and _IO_FILE* to type_list_t
  //check notes in the file struct-reader.h at https://g...content-available-to-author-only...b.com/alexpolt/luple

  using FILE_luple = luple< FILE_tlist >;

  auto& t = reinterpret_cast<FILE_luple&>( *stdout );

  printf("sizeof FILE = %zd, sizeof FILE_luple = %zd\n", sizeof(FILE), sizeof(t));

  luple_do(t, [i=0](auto& value) mutable { printf("%d. %s: %#zX, \n", i++, typeid(value).name(), (size_t)value); });

}