Last active
July 16, 2024 13:37
-
-
Save martinstarkov/6342777cdef78179697cba5e766fea51 to your computer and use it in GitHub Desktop.
Struct Member Determination (Reflection) using Luple
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #pragma once | |
| #include <utility> | |
| #include <type_traits> | |
| namespace luple_ns { | |
| //type list | |
| template<typename... TT> struct type_list { | |
| static const int size = sizeof...(TT); | |
| template<typename... UU> struct add { | |
| using type = type_list< TT..., UU... >; | |
| }; | |
| }; | |
| //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<int N> struct tlist_get< type_list<>, N, 0 > {}; | |
| 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; | |
| }; | |
| //helper template to check for a reference in a parameter pack | |
| template<typename... TT> struct has_reference; | |
| template<typename T, typename... TT> | |
| struct has_reference< T, TT... > : has_reference< TT... > {}; | |
| template<typename T, typename... TT> | |
| struct has_reference< T &, TT... > { | |
| static const bool value = true; | |
| }; | |
| template<typename T, typename... TT> | |
| struct has_reference< T &&, TT... > { | |
| static const bool value = true; | |
| }; | |
| template<> | |
| struct has_reference<> { | |
| static const bool value = false; | |
| }; | |
| //forward declaration | |
| template<typename T> struct luple_t; | |
| //for sfinae | |
| template<typename T> struct is_luple { | |
| static const bool value = false; | |
| }; | |
| template<typename T> struct is_luple< luple_t<T> > { | |
| static const bool value = true; | |
| }; | |
| //a building block that is used in multiple inheritane | |
| template<typename T, int N> struct luple_element { | |
| using value_type = tlist_get_t<T, N>; | |
| value_type _value; | |
| }; | |
| //base of luple and also parent of luple_element's | |
| template<typename T, typename U> struct luple_base; | |
| template<typename... TT, int... NN> | |
| struct luple_base< type_list<TT...>, std::integer_sequence<int, NN...> > : luple_element< type_list<TT...>, NN >... { | |
| using tlist = type_list<TT...>; | |
| //construction | |
| constexpr luple_base () {} | |
| template<typename... UU> | |
| constexpr luple_base ( UU &&... args ) : luple_element< tlist, NN >{ std::forward<UU>( args ) }... {} | |
| //converting construction | |
| template<typename U> | |
| constexpr luple_base ( luple_t<U> const & o ) : luple_element< tlist, NN >{ TT( o.template get<NN>() ) }... {} | |
| template<typename U> | |
| constexpr luple_base ( luple_t<U> && o ) : luple_element< tlist, NN >{ TT( std::move( o.template get<NN>() ) ) }... { | |
| static_assert( ! has_reference<TT...>::value, "a converting constructor can't be used with reference template parameters" ); | |
| } | |
| }; | |
| //luple implementation, T - type_list< ... > | |
| template<typename T> struct luple_t : luple_base< T, std::make_integer_sequence<int, T::size> > { | |
| using type_list = T; | |
| using base = luple_base< T, std::make_integer_sequence< int, T::size > >; | |
| static const int size = T::size; | |
| //constructing | |
| constexpr luple_t () {} | |
| template<typename... UU> | |
| constexpr luple_t ( UU &&... args ) : base{ std::forward<UU>( args )... } { | |
| static_assert( sizeof...(UU) == size, "wrong number of arguments" ); | |
| } | |
| //converting construction | |
| template<typename U> | |
| constexpr luple_t ( luple_t<U> & o ) : luple_t{ const_cast< luple_t<U> const & >( o ) } {} | |
| template<typename U> | |
| constexpr luple_t ( luple_t<U> const & o ) : base{ o } { | |
| static_assert( U::size == size, "sizes of luples do not match" ); | |
| } | |
| template<typename U> | |
| constexpr luple_t ( luple_t<U> && o ) : base{ std::move( o ) } { | |
| static_assert( U::size == size, "sizes of luples do not match" ); | |
| } | |
| //copying a different luple | |
| template<typename U> | |
| auto & operator= ( luple_t<U> const & r ) { | |
| static_assert( size == U::size, "sizes of luples do not match" ); | |
| return assign_( r, std::make_integer_sequence< int, size >{} ); | |
| } | |
| template<typename U, int... NN> | |
| auto & assign_ ( luple_t< U > const & r, std::integer_sequence< int, NN... > ) { | |
| char dummy[] = { ( get< NN >() = r.template get< NN >(), char{} )... }; | |
| (void) dummy; | |
| return *this; | |
| } | |
| //moving a different luple | |
| template<typename U> | |
| auto & operator= ( luple_t<U> && r ) { | |
| static_assert( size == U::size, "sizes of luples do not match" ); | |
| return assign_( std::move( r ), std::make_integer_sequence< int, size >{} ); | |
| } | |
| template<typename U, int... NN> | |
| auto & assign_ ( luple_t< U > && r, std::integer_sequence< int, NN... > ) { | |
| char dummy[] = { ( get< NN >() = std::move( r.template get< NN >() ), char{} )... }; | |
| (void) dummy; | |
| return *this; | |
| } | |
| //accessing data | |
| template<int N> constexpr auto & get () { | |
| static_assert( N < size, "luple::get -> out of bounds access" ); | |
| return luple_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 luple_element< T, tlist_get_n< T, U >::value >::_value; | |
| } | |
| template<int N> constexpr auto & get () const { | |
| static_assert( N < T::size, "luple::get -> out of bounds access" ); | |
| return luple_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 luple_element< T, tlist_get_n< T, U >::value >::_value; | |
| } | |
| }; | |
| //template alias to wrap types into type_list | |
| template<typename... TT> | |
| using luple = luple_t< type_list< TT... > >; | |
| //get function helpers | |
| template<int N, typename T> constexpr auto & get ( luple_t<T> & t ) { return t.template get<N>(); } | |
| template<typename U, typename T> constexpr auto & get ( luple_t<T> & t ) { return t.template get<U>(); } | |
| template<int N, typename T> constexpr auto & get ( luple_t<T> const & t ) { return t.template get<N>(); } | |
| template<typename U, typename T> constexpr auto & get ( luple_t<T> const & t ) { return t.template get<U>(); } | |
| //luple size | |
| template<typename T> constexpr auto size ( luple_t<T> const & ) { return T::size; } | |
| //member index from type | |
| template<typename U, typename T> constexpr auto index ( luple_t<T> const & ) { 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 run code for every member of luple | |
| template<int... N, typename T0, typename T1> | |
| constexpr void luple_do_impl ( std::integer_sequence<int, N...>, T0 & t, T1 fn ) { | |
| //in C++17 we got folding expressions | |
| char dummy[] = { ( fn( get<N>(t) ), char{} )... }; | |
| (void)dummy; | |
| } | |
| //helper to run code for every member of luple | |
| template<typename T0, typename T1> | |
| constexpr void luple_do ( T0 & t, T1 fn ) { | |
| luple_do_impl( std::make_integer_sequence< int, T0::type_list::size >{}, t, fn ); | |
| } | |
| //tie | |
| template<typename... TT> | |
| constexpr auto luple_tie ( TT &... args ) { | |
| return luple< TT &... >{ args... }; | |
| } | |
| //as_luple( value0, value1 ... ) -> luple< decltype(value0), decltype(value1) ... > | |
| template<typename... TT> | |
| constexpr auto as_luple ( TT... args ) { | |
| return luple< TT... >{ std::move( args )... }; | |
| } | |
| //relational operators helpers | |
| template<int N, typename T, typename U, typename = std::enable_if_t< N == T::size >> | |
| constexpr bool luple_cmp_less ( T &, U & ) { return false; } | |
| template<int N, typename T, typename U, typename = std::enable_if_t< (N < T::size) >> | |
| constexpr bool luple_cmp_less ( luple_t< T > const & a, luple_t< U > const & b ) { | |
| bool less = get<N>( a ) < get<N>( b ); | |
| bool equal = get<N>( a ) == get<N>( b ); | |
| return less ? true : ( equal ? luple_cmp_less< N+1 >( a, b ) : false ); | |
| } | |
| template<int N, typename T, typename U, typename = std::enable_if_t< N == T::size >> | |
| constexpr bool luple_cmp_equal ( T &, U & ) { return true; } | |
| template<int N, typename T, typename U, typename = std::enable_if_t< ( N < T::size ) >> | |
| constexpr bool luple_cmp_equal ( luple_t<T> const & a, luple_t<U> const & b ) { | |
| bool equal = get<N>( a ) == get<N>( b ); | |
| return equal ? luple_cmp_equal< N + 1 >( a, b ) : false; | |
| } | |
| //relational operators | |
| template<typename T, typename U> | |
| constexpr bool operator < ( luple_t<T> const & a, luple_t<U> const & b ) { | |
| static_assert( T::size > 0 && T::size == U::size, "sizes of luples don't match" ); | |
| return luple_cmp_less<0>( a, b ); | |
| } | |
| template<typename T, typename U> | |
| constexpr bool operator == ( luple_t<T> const & a, luple_t<U> const & b ) { | |
| static_assert( T::size > 0 && T::size == U::size, "sizes of luples don't match" ); | |
| return luple_cmp_equal<0>( a, b ); | |
| } | |
| //the rest are easy | |
| template<typename T, typename U> | |
| constexpr bool operator != ( luple_t<T> const & a, luple_t<U> const & b ) { return !( a == b ); } | |
| template<typename T, typename U> | |
| constexpr bool operator > ( luple_t<T> const & a, luple_t<U> const & b ) { return b < a; } | |
| template<typename T, typename U> | |
| constexpr bool operator <= ( luple_t<T> const & a, luple_t<U> const & b ) { return !( a > b ); } | |
| template<typename T, typename U> | |
| constexpr bool operator >= ( luple_t<T> const & a, luple_t<U> const & b ) { return !( a < b ); } | |
| //swap | |
| template<typename T> | |
| constexpr void swap( luple_t<T> & l, luple_t<T> & r ) { | |
| auto tmp = std::move( l ); | |
| l = std::move( r ); | |
| r = std::move( tmp ); | |
| } | |
| } | |
| //import into global namespace | |
| using luple_ns::luple; | |
| using luple_ns::luple_t; | |
| using luple_ns::get; | |
| using luple_ns::index; | |
| using luple_ns::luple_tie; | |
| using luple_ns::luple_do; | |
| using luple_ns::as_luple; | |
| namespace loophole_ns { | |
| /* | |
| tag<T,N> generates friend declarations and helps with overload resolution. | |
| There are two types: one with the auto return type, which is the way we read types later. | |
| The second one is used in the detection of instantiations without which we'd get multiple | |
| definitions. | |
| */ | |
| template<typename T, int N> | |
| struct tag { | |
| friend auto loophole(tag<T,N>); | |
| constexpr friend int cloophole(tag<T,N>); | |
| }; | |
| /* | |
| The definitions of friend functions. | |
| */ | |
| template<typename T, typename U, int N, bool B> | |
| struct fn_def { | |
| friend auto loophole(tag<T,N>) { return U{}; } | |
| constexpr friend int cloophole(tag<T,N>) { return 0; } | |
| }; | |
| /* | |
| This specialization is to avoid multiple definition errors. | |
| */ | |
| template<typename T, typename U, int N> | |
| struct fn_def<T, U, N, true> {}; | |
| /* | |
| This has a templated conversion operator which in turn triggers instantiations. | |
| Important point, using sizeof seems to be more reliable. Also default template | |
| arguments are "cached" (I think). To fix that I provide a U template parameter to | |
| the ins functions which do the detection using constexpr friend functions and SFINAE. | |
| */ | |
| template<typename T, int N> | |
| struct c_op { | |
| template<typename U, int M> static auto ins(...) -> int; | |
| template<typename U, int M, int = cloophole(tag<T,M>{}) > static auto ins(int) -> char; | |
| template<typename U, int = sizeof(fn_def<T, U, N, sizeof(ins<U, N>(0)) == sizeof(char)>)> | |
| operator U(); | |
| }; | |
| /* | |
| Here we detect the data type field number. The byproduct is instantiations. | |
| Uses list initialization. Won't work for types with user provided constructors. | |
| In C++17 there is std::is_aggregate which can be added later. | |
| */ | |
| template<typename T, int... NN> | |
| constexpr int fields_number(...) { return sizeof...(NN)-1; } | |
| template<typename T, int... NN> | |
| constexpr auto fields_number(int) -> decltype(T{ c_op<T,NN>{}... }, 0) { | |
| return fields_number<T, NN..., sizeof...(NN)>(0); | |
| } | |
| /* | |
| This is a helper to turn a data structure into a type list. | |
| Usage is: loophole_ns::as_type_list< data_t > | |
| I keep dependency on luple (a lightweight tuple of my design) because it's handy | |
| to turn a structure into luple (tuple). luple has the advantage of more stable layout | |
| across compilers and we can reinterpret_cast between the data structure and luple. | |
| More details are in the luple.h header. | |
| */ | |
| template<typename T, typename U> | |
| struct loophole_type_list; | |
| template<typename T, int... NN> | |
| struct loophole_type_list< T, std::integer_sequence<int, NN...> > { | |
| using type = luple_ns::type_list< decltype(loophole(tag<T, NN>{}))... >; | |
| }; | |
| template<typename T> | |
| using as_type_list = | |
| typename loophole_type_list<T, std::make_integer_sequence<int, fields_number<T>(0)>>::type; | |
| } | |
| #include <iostream> | |
| #include <string> | |
| #include <vector> | |
| #include <array> | |
| #include <type_traits> | |
| template <typename T> | |
| std::string GetType() { | |
| if constexpr (std::is_same_v<T, double>) { | |
| return "double"; | |
| } else if constexpr (std::is_same_v<T, float>) { | |
| return "float"; | |
| } else if constexpr (std::is_same_v<T, int>) { | |
| return "int"; | |
| } else if constexpr (std::is_same_v<T, unsigned int>) { | |
| return "uint"; | |
| } else if constexpr (std::is_same_v<T, bool>) { | |
| return "bool"; | |
| } | |
| return "unknown"; | |
| } | |
| int main() { | |
| using float_ = std::array<float, 1>; | |
| using vec2 = std::array<float, 2>; | |
| using vec3 = std::array<float, 3>; | |
| using vec4 = std::array<float, 4>; | |
| using double_ = std::array<double, 1>; | |
| using dvec2 = std::array<double, 2>; | |
| using dvec3 = std::array<double, 3>; | |
| using dvec4 = std::array<double, 4>; | |
| using bool_ = std::array<bool, 1>; | |
| using bvec2 = std::array<bool, 2>; | |
| using bvec3 = std::array<bool, 3>; | |
| using bvec4 = std::array<bool, 4>; | |
| using int_ = std::array<int, 1>; | |
| using ivec2 = std::array<int, 2>; | |
| using ivec3 = std::array<int, 3>; | |
| using ivec4 = std::array<int, 4>; | |
| using uint_ = std::array<unsigned int, 1>; | |
| using uvec2 = std::array<unsigned int, 2>; | |
| using uvec3 = std::array<unsigned int, 3>; | |
| using uvec4 = std::array<unsigned int, 4>; | |
| struct Vertex { | |
| float_ test1; | |
| vec2 test2; | |
| vec3 test3; | |
| vec4 test4; | |
| double_ test5; | |
| dvec2 test6; | |
| dvec3 test7; | |
| dvec4 test8; | |
| bool_ test9; | |
| bvec2 test10; | |
| bvec3 test11; | |
| bvec4 test12; | |
| int_ test13; | |
| ivec2 test14; | |
| ivec3 test15; | |
| ivec4 test16; | |
| uint_ test17; | |
| uvec2 test18; | |
| uvec3 test19; | |
| uvec4 test20; | |
| }; | |
| using data_tlist = loophole_ns::as_type_list<Vertex>; | |
| using data_luple = luple_t< data_tlist >; | |
| Vertex v{}; | |
| auto& l = reinterpret_cast< data_luple& >(v); | |
| luple_do( l, []( auto& value ) { std::cout << "count: " << value.size() << ", size: " << sizeof(decltype(value)) / value.size() << ", type: " << GetType<typename std::remove_reference_t<decltype(value)>::value_type>() << std::endl; } ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment