forked from Alepha/Alepha
Partially on the way to complete 17 reflection.
I can't figure out why my computation on empty bases is 1 higher than it should be -- so I'm punting, for now. I'll revisit it when I have more time.
This commit is contained in:
72
Reflection/aggregate_initialization.h
Normal file
72
Reflection/aggregate_initialization.h
Normal file
@ -0,0 +1,72 @@
|
||||
static_assert( __cplusplus > 201700, "C++17 Required" );
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Alepha/Alepha.h>
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
#include <Alepha/Reflection/detail/config.h>
|
||||
|
||||
namespace Alepha::Hydrogen::Reflection
|
||||
{
|
||||
inline namespace exports { inline namespace aggregate_initialization {} }
|
||||
|
||||
namespace detail::aggregate_initialization
|
||||
{
|
||||
inline namespace exports {}
|
||||
|
||||
// Basic methodology here: I probe the number of arguments that an object can be constructed with. I don't
|
||||
// care what they actually are, as there's no easy way to hoist out what they are. However, for an aggregate,
|
||||
// the largest number of items one can initialize it with is related to the number of items that it contains.
|
||||
// Therefore as long as the inspection is performed on aggregates and yields the maximum number it finds, then
|
||||
// it will always yield a valid initialization pack count. Other information is then used to winnow that
|
||||
// down to arrive at a member count.
|
||||
|
||||
// The basic adaptable argument. Because it pretends to be anything, it can be used as a parameter in invoking
|
||||
// any initialization method.
|
||||
struct argument
|
||||
{
|
||||
template< typename T > constexpr operator T ();
|
||||
};
|
||||
|
||||
// The first step is to just start it all off with a blank sequence and walk forward from there.
|
||||
// The default arguments cause it to start with the blank sequence, even if it doesn't match this
|
||||
// case in the specialization selection.
|
||||
template< typename T, typename seq= std::index_sequence<>, typename= void, typename= std::enable_if_t< std::is_aggregate_v< T > > >
|
||||
struct init_count_impl
|
||||
// When this base case is reached, the size of the sequence is the argument count.
|
||||
: std::integral_constant< std::size_t, seq::size() > {};
|
||||
|
||||
// This expansion case always matches when an initializer of the number of elements in the sequence is syntactically
|
||||
// valid. It also recurses, thus exploring the whole initializer set. There is have one fewer value in the sequence set
|
||||
// than we use to initialize so that when SFINAE gives up, it defers to the base case above, thus having the right
|
||||
// count.
|
||||
template< typename T, std::size_t ... values >
|
||||
struct init_count_impl
|
||||
<
|
||||
T,
|
||||
std::index_sequence< values... >,
|
||||
std::void_t< decltype( T{ ( values, std::declval< argument >() )..., std::declval< argument >() } ) >,
|
||||
void
|
||||
>
|
||||
// Descend and take the next element in the sequence.
|
||||
: init_count_impl< T, std::index_sequence< values..., sizeof...( values ) > > {};
|
||||
|
||||
namespace exports
|
||||
{
|
||||
template< typename T >
|
||||
constexpr std::size_t aggregate_initializer_size_v= init_count_impl< T >::value;
|
||||
|
||||
template< typename T >
|
||||
using aggregate_initializer_size= typename init_count_impl< T >::type;
|
||||
}
|
||||
}
|
||||
|
||||
namespace exports::aggregate_initialization
|
||||
{
|
||||
using namespace detail::aggregate_initialization::exports;
|
||||
}
|
||||
}
|
135
Reflection/aggregate_members.h
Normal file
135
Reflection/aggregate_members.h
Normal file
@ -0,0 +1,135 @@
|
||||
static_assert( __cplusplus > 201700, "C++17 Required" );
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Alepha/Alepha.h>
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
#include <Alepha/Reflection/detail/config.h>
|
||||
#include <Alepha/Reflection/aggregate_initialization.h>
|
||||
|
||||
#include <Alepha/Meta/overload.h>
|
||||
|
||||
namespace Alepha::Hydrogen::Reflection
|
||||
{
|
||||
inline namespace exports { inline namespace aggregate_members {} }
|
||||
|
||||
namespace detail::aggregate_members
|
||||
{
|
||||
inline namespace exports {}
|
||||
|
||||
using Meta::overload;
|
||||
|
||||
// Basic methodology here: The number of members in an aggregate is equal to the number of initializer parameters
|
||||
// it takes less the number of empty base classes it has.
|
||||
|
||||
// The basic adaptable argument. Because it pretends to be anything, it can be used as a parameter in invoking
|
||||
// any initialization method.
|
||||
struct argument { template< typename T > constexpr operator T (); };
|
||||
|
||||
template< typename T >
|
||||
struct checker
|
||||
{
|
||||
using type= typename checker< T >::type;
|
||||
//using type= void;
|
||||
//static_assert( std::is_empty_v< T > );
|
||||
};
|
||||
|
||||
// Any empty-base-class argument.
|
||||
template< typename Aggregate >
|
||||
struct empty_base
|
||||
{
|
||||
template
|
||||
<
|
||||
typename T,
|
||||
//typename= typename checker< std::decay_t< T > >::type,
|
||||
typename= std::enable_if_t< std::is_empty_v< std::decay_t< T > > >,
|
||||
typename= std::enable_if_t< not std::is_same_v< std::decay_t< T >, Aggregate > >,
|
||||
typename= std::enable_if_t< std::is_base_of_v< std::decay_t< T >, Aggregate > >,
|
||||
overload< __LINE__ > = nullptr
|
||||
>
|
||||
constexpr operator T ();
|
||||
|
||||
//template< typename T > constexpr operator T ()= delete;
|
||||
};
|
||||
|
||||
template< typename Tuple, std::size_t baseCount, std::size_t totalCount >
|
||||
constexpr void
|
||||
check_tuple()
|
||||
{
|
||||
static_assert( std::tuple_size_v< Tuple > == totalCount );
|
||||
}
|
||||
|
||||
template< typename Aggregate, std::size_t bases, std::size_t total >
|
||||
constexpr auto
|
||||
build_init_tuple()
|
||||
{
|
||||
static_assert( bases <= total );
|
||||
if constexpr( total == 0 ) return std::tuple{};
|
||||
else if constexpr( bases > 0 )
|
||||
{
|
||||
auto result= std::tuple_cat( std::tuple{ empty_base< Aggregate >{} }, build_init_tuple< Aggregate, bases - 1, total - 1 >() );
|
||||
check_tuple< decltype( result ), bases, total >();
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert( bases == 0 );
|
||||
auto result= std::tuple_cat( std::tuple{ argument{} }, build_init_tuple< Aggregate, 0, total - 1 >() );
|
||||
check_tuple< decltype( result ), bases, total >();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
template< typename T, typename Tuple, typename= void >
|
||||
struct is_constructible_from_tuple : std::false_type {};
|
||||
|
||||
template< typename T, typename ... TupleArgs >
|
||||
struct is_constructible_from_tuple
|
||||
<
|
||||
T,
|
||||
std::tuple< TupleArgs... >,
|
||||
std::void_t< decltype( T{ std::declval< TupleArgs >()... } ) >
|
||||
>
|
||||
: std::true_type {};
|
||||
|
||||
template< typename T, typename Tuple >
|
||||
constexpr bool is_constructible_from_tuple_v= is_constructible_from_tuple< T, Tuple >::value;
|
||||
|
||||
template< typename T, std::size_t index= 0, typename= std::enable_if_t< std::is_aggregate_v< T > > >
|
||||
constexpr std::size_t
|
||||
count_empty_bases()
|
||||
{
|
||||
constexpr auto init_size= aggregate_initializer_size_v< T >;
|
||||
|
||||
if constexpr( is_constructible_from_tuple_v< T, decltype( build_init_tuple< T, index, init_size >() ) > )
|
||||
{
|
||||
return 1 + count_empty_bases< T, index + 1 >();
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
|
||||
namespace exports
|
||||
{
|
||||
template< typename T >
|
||||
struct aggregate_empty_bases : std::integral_constant< std::size_t, count_empty_bases< T >() - 1 > {};
|
||||
|
||||
template< typename T >
|
||||
constexpr std::size_t aggregate_empty_bases_v= aggregate_empty_bases< T >::value;
|
||||
|
||||
template< typename T >
|
||||
constexpr std::size_t aggregate_member_count_v= aggregate_initializer_size_v< T > - aggregate_empty_bases_v< T >;
|
||||
|
||||
template< typename T >
|
||||
struct aggregate_member_count : std::integral_constant< std::size_t, aggregate_member_count_v< T > > {};
|
||||
}
|
||||
}
|
||||
|
||||
namespace exports::aggregate_members
|
||||
{
|
||||
using namespace detail::aggregate_members::exports;
|
||||
}
|
||||
}
|
8
Reflection/detail/config.h
Normal file
8
Reflection/detail/config.h
Normal file
@ -0,0 +1,8 @@
|
||||
static_assert( __cplusplus > 201700, "C++17 Required" );
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Alepha/Alepha.h>
|
||||
|
||||
// This file will eventually contain all of the constants that control how much generated code the
|
||||
// C++17 Reflection system will geeerate.
|
182
Reflection/tuplizeAggregate.h
Normal file
182
Reflection/tuplizeAggregate.h
Normal file
@ -0,0 +1,182 @@
|
||||
static_assert( __cplusplus > 201700, "C++17 Required" );
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Alepha/Alepha.h>
|
||||
|
||||
#include <boost/preprocessor.hpp>
|
||||
|
||||
#include <Alepha/Reflection/detail/config.h>
|
||||
#include <Alepha/Reflection/aggregate_members.h>
|
||||
|
||||
namespace Alepha::Hydrogen::Reflection
|
||||
{
|
||||
inline namespace exports { inline namespace tuplize_aggregate {} }
|
||||
|
||||
namespace detail::tuplize_aggregate
|
||||
{
|
||||
inline namespace exports
|
||||
{
|
||||
/*!
|
||||
* Deconstruct an aggregate object into a tie-based tuple pointing at its members.
|
||||
*
|
||||
* C++17's primary new reflection-oriented introduction is Structured Binding Declarations.
|
||||
* What these let one do is to introduce a set of named variables that bind to each member
|
||||
* of a (raw aggregate) `struct` in turn. This leads to some very interesting forms of
|
||||
* "reflection" about what a user defined type is made of. Combined with the `std::is_aggregate`
|
||||
* trait function and a way to determine the number of member objects, this provides a
|
||||
* powerful new way to inspect any type.
|
||||
*
|
||||
* Structured Binding Declarations can also be used with arrays or types which implement a subset
|
||||
* of the `std::tuple` interface. Those cases are not as interesting. We've always had the
|
||||
* ability to inspect arrays via templates -- simple deduction operations work for that. And
|
||||
* C++11's `std::tuple`s are already inspectable by their nature and types which implement a tuple-like
|
||||
* interface are also easily inspected by pre-C++17 means.
|
||||
*
|
||||
* The most important thing C++17 Structured Binding brings to the language is the ability to
|
||||
* (at compiletime) programmatically inspect any structure's members -- to learn their types,
|
||||
* and with a bit of special effort, to learn their offsets. The names of those members are
|
||||
* hidden, but their types are available, as is a way to work with all of them at once. Any
|
||||
* Structured Binding is sufficient to do this -- one need only give a new name for each member of
|
||||
* the type. `auto &[ a, b, c, d ]= someStruct;` is all that is needed and one has already performed
|
||||
* an interesting feat of rudimentary reflection on the type `someStruct`. By loading those values
|
||||
* into a tuple (by reference), by code such as `std::tie( a, b, c, d )`, a programmer can provide
|
||||
* an anonymized, distilled reflection of the contents of that `struct`. This said, a library function
|
||||
* which can decompose any `struct` into such a tie is very useful. `tuplizeAggregate` is exactly this.
|
||||
*
|
||||
* This function contains a pre-built set of such decompositions for structs of various sizes. C++17
|
||||
* does not permit arbitrarily sized Structured Bindings, and so a limit had to be placed. The limit
|
||||
* is fairly generous, however. If an aggregate size which is greater than the pre-build maximum is
|
||||
* provided, then the compile will fail on a `static_assert` indicating this.
|
||||
*
|
||||
* Unfortunately, as a declaration syntax, the number of members in a `struct`'s body cannot be inferred
|
||||
* through SFINAE by this means. Normally the user must explicitly provide the number of member
|
||||
* variables. However, combined with a pair of C++11 features (based upon variadic templates and
|
||||
* aggregate initialization syntax) we can infer the number of memmber values via a set of helper
|
||||
* templates (which can also be called directly.)
|
||||
*
|
||||
* This kind of reflection into an aggregate type can prove very useful. Code generators for
|
||||
* serialization, conversion tools, universal utility functions, and much more can all be built in
|
||||
* C++17, today, using this kind of reflection! There's no need to wait for C++23 or beyond when
|
||||
* static reflection is added to the language. A great deal of desired reflection use cases can be
|
||||
* attained today. One just need write some code generators in terms of `std::tuple` and `std::tie`,
|
||||
* then make any overloads (perhaps using ADL hooking tricks) which call `Alepha::Reflection::tuplizeAggregate`
|
||||
* and pass that result to the general tuple form. For serializers and such, other techniques such as
|
||||
* `boost::core::demangle( typeid( instance ).name() )` can be used to get nice names for types when
|
||||
* implementing universal serializers. In fact, this can be used as a crutch for serializing more
|
||||
* complicated user types (with private data and such). Those types can produce an aggregate "view"
|
||||
* of what they must serialize or deserialize, and then they can hand that view off to such code
|
||||
* generators. And, of cousre, this need not apply just to serialization.
|
||||
*
|
||||
* @param agg Aggregate instance to decompose into a `std::tie` based `std::tuple`.
|
||||
* @tparam aggregate_size The number of members in the aggregate argument `agg`'s definition.
|
||||
* @tparam Aggregate The type of the aggregate to decompose.
|
||||
*/
|
||||
// TODO: Make `aggregate_size` deduced via `Reflection::aggregate_ctor...` means.
|
||||
template< std::size_t aggregate_size, typename Aggregate, typename= std::enable_if_t< not std::is_rvalue_reference_v< Aggregate > > >
|
||||
constexpr decltype( auto )
|
||||
tuplizeAggregate( Aggregate &&agg )
|
||||
{
|
||||
static_assert( std::is_aggregate_v< std::decay_t< Aggregate > >, "`tuplizeAggregate` only can be used on aggregates" );
|
||||
|
||||
// TODO: Generate these cases via boost preprocessor, to cut down on repetition...
|
||||
if constexpr( aggregate_size == 0 ) return std::tuple{};
|
||||
else if constexpr( aggregate_size == 1 )
|
||||
{
|
||||
auto &[ a0 ]= agg;
|
||||
return std::tie( a0 );
|
||||
}
|
||||
else if constexpr( aggregate_size == 2 )
|
||||
{
|
||||
auto &[ a0, a1 ]= agg;
|
||||
return std::tie( a0, a1 );
|
||||
}
|
||||
else if constexpr( aggregate_size == 3 )
|
||||
{
|
||||
auto &[ a0, a1, a2 ]= agg;
|
||||
return std::tie( a0, a1, a2 );
|
||||
}
|
||||
else if constexpr( aggregate_size == 4 )
|
||||
{
|
||||
auto &[ a0, a1, a2, a3 ]= agg;
|
||||
return std::tie( a0, a1, a2, a3 );
|
||||
}
|
||||
else if constexpr( aggregate_size == 5 )
|
||||
{
|
||||
auto &[ a0, a1, a2, a3, a4 ]= agg;
|
||||
return std::tie( a0, a1, a2, a3, a4 );
|
||||
}
|
||||
else if constexpr( aggregate_size == 6 )
|
||||
{
|
||||
auto &[ a0, a1, a2, a3, a4, a5 ]= agg;
|
||||
return std::tie( a0, a1, a2, a3, a4, a5 );
|
||||
}
|
||||
else if constexpr( aggregate_size == 7 )
|
||||
{
|
||||
auto &[ a0, a1, a2, a3, a4, a5, a6 ]= agg;
|
||||
return std::tie( a0, a1, a2, a3, a4, a5, a6 );
|
||||
}
|
||||
else if constexpr( aggregate_size == 8 )
|
||||
{
|
||||
auto &[ a0, a1, a2, a3, a4, a5, a6, a7 ]= agg;
|
||||
return std::tie( a0, a1, a2, a3, a4, a5, a6, a7 );
|
||||
}
|
||||
else if constexpr( aggregate_size == 9 )
|
||||
{
|
||||
auto &[ a0, a1, a2, a3, a4, a5, a6, a7, a8 ]= agg;
|
||||
return std::tie( a0, a1, a2, a3, a4, a5, a6, a7, a8 );
|
||||
}
|
||||
else if constexpr( aggregate_size == 10 )
|
||||
{
|
||||
auto &[ a0, a1, a2, a3, a4, a5, a6, a7, a8, a9 ]= agg;
|
||||
return std::tie( a0, a1, a2, a3, a4, a5, a6, a7, a8, a9 );
|
||||
}
|
||||
else if constexpr( aggregate_size == 11 )
|
||||
{
|
||||
auto &[ a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10 ]= agg;
|
||||
return std::tie( a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10 );
|
||||
}
|
||||
else if constexpr( aggregate_size == 12 )
|
||||
{
|
||||
auto &[ a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11 ]= agg;
|
||||
return std::tie( a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11 );
|
||||
}
|
||||
else if constexpr( aggregate_size == 13 )
|
||||
{
|
||||
auto &[ a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 ]= agg;
|
||||
return std::tie( a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 );
|
||||
}
|
||||
else if constexpr( aggregate_size == 14 )
|
||||
{
|
||||
auto &[ a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13 ]= agg;
|
||||
return std::tie( a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13 );
|
||||
}
|
||||
else if constexpr( aggregate_size == 15 )
|
||||
{
|
||||
auto &[ a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14 ]= agg;
|
||||
return std::tie( a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14 );
|
||||
}
|
||||
else if constexpr( aggregate_size == 16 )
|
||||
{
|
||||
auto &[ a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 ]= agg;
|
||||
return std::tie( a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 );
|
||||
}
|
||||
// Impossible, in this case -- we would have taken the original 0 branch were this so.
|
||||
else static_assert( aggregate_size == 0, "The specified aggregate has more members than `tuplizeAggregate` can handle" );
|
||||
}
|
||||
|
||||
// This overload deduces the aggregate size using the initializer inspection utilities.
|
||||
template< typename Aggregate >
|
||||
constexpr decltype( auto )
|
||||
tuplizeAggregate( Aggregate &&agg )
|
||||
{
|
||||
return tuplizeAggregate< aggregate_member_count_v< std::decay_t< Aggregate > > >( std::forward< Aggregate >( agg ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace exports::tuplize_aggregate
|
||||
{
|
||||
using namespace detail::tuplize_aggregate::exports;
|
||||
}
|
||||
}
|
77
Reflection/tuplizeAggregate.test/0.cc
Normal file
77
Reflection/tuplizeAggregate.test/0.cc
Normal file
@ -0,0 +1,77 @@
|
||||
static_assert( __cplusplus > 201700, "C++17 Required" );
|
||||
|
||||
#include <Alepha/Reflection/tuplizeAggregate.h>
|
||||
|
||||
#include <Alepha/Testing/test.h>
|
||||
#include <Alepha/types.h>
|
||||
#include <Alepha/Meta/product_type_decay.h>
|
||||
|
||||
using Alepha::argcnt_t, Alepha::argvec_t;
|
||||
|
||||
int
|
||||
main( const argcnt_t argcnt, const argvec_t argvec )
|
||||
{
|
||||
return Alepha::Testing::runAllTests( argcnt, argvec );
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace Alepha::Testing::literals;
|
||||
struct instance
|
||||
{
|
||||
int a;
|
||||
float b;
|
||||
char c;
|
||||
double d;
|
||||
};
|
||||
|
||||
static_assert( std::tuple_size_v< decltype( Alepha::Reflection::tuplizeAggregate( std::declval< instance >() ) ) > == 4 );
|
||||
static_assert( Alepha::Reflection::aggregate_empty_bases_v< instance > == 0 );
|
||||
static_assert( std::is_same_v
|
||||
<
|
||||
Alepha::Meta::product_type_decay_t< decltype( Alepha::Reflection::tuplizeAggregate( std::declval< instance >() ) ) >,
|
||||
std::tuple< int, float, char, double >
|
||||
> );
|
||||
|
||||
struct instance2 : instance
|
||||
{
|
||||
int a;
|
||||
float b;
|
||||
char c;
|
||||
double d;
|
||||
};
|
||||
|
||||
// Apparently decomposibility is not quite the same as aggregate.
|
||||
// We'll need a way to do this right?
|
||||
//static_assert( not std::is_aggregate_v< instance2 > );
|
||||
|
||||
struct empty1 {};
|
||||
struct empty2 {};
|
||||
struct empty3 {};
|
||||
|
||||
struct instance3 : empty1, empty2
|
||||
{
|
||||
empty3 e;
|
||||
int a;
|
||||
float b;
|
||||
char c;
|
||||
double d;
|
||||
};
|
||||
|
||||
static_assert( Alepha::Reflection::aggregate_initializer_size_v< instance3 > == 7 );
|
||||
static_assert( Alepha::Reflection::aggregate_empty_bases_v< instance3 > == 2 );
|
||||
auto t= "test"_test <=[]
|
||||
{
|
||||
using namespace Alepha::Reflection::detail::aggregate_members;
|
||||
std::cout << Alepha::Reflection::aggregate_empty_bases_v< instance3 > << std::endl;
|
||||
|
||||
static_assert( std::is_empty_v< empty1 > );
|
||||
static_assert( is_constructible_from_tuple_v< instance3, std::tuple< empty_base< instance3 >, empty_base< instance3 > > > );
|
||||
};
|
||||
static_assert( std::tuple_size_v< decltype( Alepha::Reflection::tuplizeAggregate( std::declval< instance3 >() ) ) > == 5 );
|
||||
static_assert( std::is_same_v
|
||||
<
|
||||
Alepha::Meta::product_type_decay_t< decltype( Alepha::Reflection::tuplizeAggregate( std::declval< instance3 >() ) ) >,
|
||||
std::tuple< empty3, int, float, char, double >
|
||||
> );
|
||||
}
|
4
Reflection/tuplizeAggregate.test/Makefile
Normal file
4
Reflection/tuplizeAggregate.test/Makefile
Normal file
@ -0,0 +1,4 @@
|
||||
CXXFLAGS+= -std=c++17 -I .
|
||||
CXXFLAGS+= -g -O0
|
||||
|
||||
all: 0
|
Reference in New Issue
Block a user