1
0
forked from Alepha/Alepha

Apply the newer namespace rules and layout/formatting.

This commit is contained in:
2024-04-02 23:32:02 -04:00
parent 53a4d91a23
commit 373b07e1c4
31 changed files with 501 additions and 536 deletions

View File

@ -10,7 +10,7 @@ static_assert( __cplusplus > 2020'99 );
#include <Alepha/Reflection/detail/config.h>
namespace Alepha::Hydrogen::Reflection ::detail:: aggregate_initializer_size_m
namespace Alepha::Hydrogen::Reflection ::detail:: aggregate_initializer_size_m
{
inline namespace exports {}

View File

@ -16,176 +16,171 @@ static_assert( __cplusplus > 2020'99 );
#include <Alepha/Meta/overload.h>
#include <Alepha/Meta/type_value.h>
namespace Alepha::Hydrogen::Reflection
namespace Alepha::Hydrogen::Reflection ::detail:: aggregate_members_m
{
inline namespace exports { inline namespace aggregate_members {} }
inline namespace exports {}
namespace detail::aggregate_members
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. In simple terms, this would be `init_terms< T > - empty_bases< T >`,
* However, it's not that simple. To do that the easy way, one might need get `std::tuple< InitTerms... >` and then compute
* which terms were bases. Now that gets a bit complicated, as in C++ one can't just directly get a tuple of initializer
* arguments (portably) to scoop out the arguments and analyze them one-by-one. One can, however, constrain the arguments
* one-by-one in templates. Those constraints cannot directly leak out the types they conclude, as that requires side
* effects. (Yes, template-friend-injection can be used here, but these mechanisms are extremely delicate. They're
* not really portable. Further, the way that constraints get instantiated for matching is prone to complications.)
*
* Instead, a side-stepping approach is required. It's trivial to ask: "Can this object be constructed from these
* N adaptive types, where the first one is constrained to be a base class of your object's type?" If yes, then
* this proves a (likely) empty base. One can just recursively iterate through more an more constrained adaptive
* types until the first non-base type is reached. At this point, there are no more than that many base classes.
*
* There may actually be fewer base classes, however. Consider:
*
* ```
* struct SneakyBase {};
*
* struct Complicated : SneakyBase
* {
* SneakyBase member;
* };
* ```
* In that case, a constrained adaptable argument would see two base types. Here is where a bit of C++ trivia and
* knowledge comes into play. C++ forbids repetition of a base class's type. Therefore the sequence of base classes
* cannot have repeats. The solution is to perform a nested exploration of instantiations of `checker` types which
* has each descent disable casting to `std::is_base_of_v` types which already have been expanded. Thus whatever
* count this expands to, it will be the correct empty-bases count. Then that count can be subtracted from the
* initializer list count.
*
* Note that this will not work with types that have non-empty bases, but those types cannot be decomposed,
* anyhow. Such types cannot have C++17 reflection performed on them.
*
* For the moment, computing a deep-dive on constrainted adaptable arguments is skipped. It's a lot more
* complicated than just counting empty bases. As long as the first actual member is not also a base class,
* this technique will work.
*/
// 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 (); };
// Any empty-base-class argument.
template< typename Aggregate >
struct empty_base
{
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. In simple terms, this would be `init_terms< T > - empty_bases< T >`,
* However, it's not that simple. To do that the easy way, one might need get `std::tuple< InitTerms... >` and then compute
* which terms were bases. Now that gets a bit complicated, as in C++ one can't just directly get a tuple of initializer
* arguments (portably) to scoop out the arguments and analyze them one-by-one. One can, however, constrain the arguments
* one-by-one in templates. Those constraints cannot directly leak out the types they conclude, as that requires side
* effects. (Yes, template-friend-injection can be used here, but these mechanisms are extremely delicate. They're
* not really portable. Further, the way that constraints get instantiated for matching is prone to complications.)
*
* Instead, a side-stepping approach is required. It's trivial to ask: "Can this object be constructed from these
* N adaptive types, where the first one is constrained to be a base class of your object's type?" If yes, then
* this proves a (likely) empty base. One can just recursively iterate through more an more constrained adaptive
* types until the first non-base type is reached. At this point, there are no more than that many base classes.
*
* There may actually be fewer base classes, however. Consider:
*
* ```
* struct SneakyBase {};
*
* struct Complicated : SneakyBase
* {
* SneakyBase member;
* };
* ```
* In that case, a constrained adaptable argument would see two base types. Here is where a bit of C++ trivia and
* knowledge comes into play. C++ forbids repetition of a base class's type. Therefore the sequence of base classes
* cannot have repeats. The solution is to perform a nested exploration of instantiations of `checker` types which
* has each descent disable casting to `std::is_base_of_v` types which already have been expanded. Thus whatever
* count this expands to, it will be the correct empty-bases count. Then that count can be subtracted from the
* initializer list count.
*
* Note that this will not work with types that have non-empty bases, but those types cannot be decomposed,
* anyhow. Such types cannot have C++17 reflection performed on them.
*
* For the moment, computing a deep-dive on constrainted adaptable arguments is skipped. It's a lot more
* complicated than just counting empty bases. As long as the first actual member is not also a base class,
* this technique will work.
*/
// 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 (); };
// Any empty-base-class argument.
template< typename Aggregate >
struct empty_base
{
template< typename T >
requires
(
true
and EmptyType< std::decay_t< T > >
and not SameAs< std::decay_t< T >, Aggregate >
and DerivedFrom< Aggregate, std::decay_t< T > >
)
constexpr operator T ();
//template< typename T > constexpr operator T ()= delete;
};
template< typename T >
constexpr bool is_empty_base_v= false;
requires
(
true
and EmptyType< std::decay_t< T > >
and not SameAs< std::decay_t< T >, Aggregate >
and DerivedFrom< Aggregate, std::decay_t< T > >
)
constexpr operator T ();
template< typename T >
constexpr bool is_empty_base_v< empty_base< T > >{ true };
//template< typename T > constexpr operator T ()= delete;
};
template< typename Tuple, std::size_t baseCount, std::size_t totalCount >
constexpr void
check_tuple()
template< typename T >
constexpr bool is_empty_base_v= false;
template< typename T >
constexpr bool is_empty_base_v< empty_base< T > >{ true };
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 )
{
static_assert( std::tuple_size_v< Tuple > == totalCount );
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;
}
template< typename Aggregate, std::size_t bases, std::size_t total >
constexpr auto
build_init_tuple()
else
{
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 >
constexpr bool is_constructible_from_tuple_v= false;
template< typename T, typename ... TupleArgs >
constexpr bool is_constructible_from_tuple_v< T, std::tuple< TupleArgs... > >
{
ConstructibleFrom< T, TupleArgs... >
};
template< Aggregate T, typename InitTuple, std::size_t index= 0 >
constexpr auto
build_base_tuple()
{
constexpr auto init_size= aggregate_initializer_size_v< T >;
using DeeperTuple= decltype( build_init_tuple< T, index, init_size >() );
if constexpr( is_constructible_from_tuple_v< T, DeeperTuple > )
{
return build_base_tuple< T, DeeperTuple, index + 1 >();
}
else return Meta::type_value< InitTuple >{};
}
template< typename ... Args, typename First >
constexpr std::size_t
count_empty_bases( Meta::type_value< std::tuple< First, Args... > > )
{
if constexpr( is_empty_base_v< First > ) return 1 + count_empty_bases( Meta::type_value< std::tuple< Args... > >{} );
else return 0;
}
constexpr std::size_t
count_empty_bases( Meta::type_value< std::tuple<> > )
{
return 0;
}
template< Aggregate T, std::size_t index= 0 >
constexpr std::size_t
count_empty_bases()
{
return count_empty_bases( build_base_tuple< T, decltype( build_init_tuple< T, 0, aggregate_initializer_size_v< T > > ) >() );
}
namespace exports
{
template< typename T >
struct aggregate_empty_bases : std::integral_constant< std::size_t, count_empty_bases< T >() > {};
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 > > {};
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;
}
}
namespace exports::aggregate_members
template< typename T, typename Tuple >
constexpr bool is_constructible_from_tuple_v= false;
template< typename T, typename ... TupleArgs >
constexpr bool is_constructible_from_tuple_v< T, std::tuple< TupleArgs... > >
{
using namespace detail::aggregate_members::exports;
ConstructibleFrom< T, TupleArgs... >
};
template< Aggregate T, typename InitTuple, std::size_t index= 0 >
constexpr auto
build_base_tuple()
{
constexpr auto init_size= aggregate_initializer_size_v< T >;
using DeeperTuple= decltype( build_init_tuple< T, index, init_size >() );
if constexpr( is_constructible_from_tuple_v< T, DeeperTuple > )
{
return build_base_tuple< T, DeeperTuple, index + 1 >();
}
else return Meta::type_value< InitTuple >{};
}
template< typename ... Args, typename First >
constexpr std::size_t
count_empty_bases( Meta::type_value< std::tuple< First, Args... > > )
{
if constexpr( is_empty_base_v< First > ) return 1 + count_empty_bases( Meta::type_value< std::tuple< Args... > >{} );
else return 0;
}
constexpr std::size_t
count_empty_bases( Meta::type_value< std::tuple<> > )
{
return 0;
}
template< Aggregate T, std::size_t index= 0 >
constexpr std::size_t
count_empty_bases()
{
return count_empty_bases( build_base_tuple< T, decltype( build_init_tuple< T, 0, aggregate_initializer_size_v< T > > ) >() );
}
namespace exports
{
template< typename T >
struct aggregate_empty_bases : std::integral_constant< std::size_t, count_empty_bases< T >() > {};
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 Alepha::Hydrogen::Reflection::inline exports::inline aggregate_members_m
{
using namespace detail::aggregate_members_m::exports;
}

View File

@ -11,197 +11,192 @@ static_assert( __cplusplus > 2020'99 );
#include <Alepha/Reflection/detail/config.h>
#include <Alepha/Reflection/aggregate_members.h>
namespace Alepha::Hydrogen::Reflection
namespace Alepha::Hydrogen::Reflection ::detail:: tuplizeAggregate_m
{
inline namespace exports { inline namespace tuplize_aggregate {} }
inline namespace exports {}
namespace detail::tuplize_aggregate
template< typename T >
concept SalientMembers=
requires( const T &t )
{
inline namespace exports {}
{ T::salient_members } -> ConvertibleTo< std::size_t >;
};
template< typename T >
concept SalientMembers=
requires( const T &t )
{
{ T::salient_members } -> ConvertibleTo< std::size_t >;
};
template< typename T >
constexpr std::size_t
compute_salient_members_count_impl()
{
if constexpr( SalientMembers< T > ) return T::salient_members;
else return aggregate_member_count_v< std::decay_t< T > >;
}
template< typename T >
constexpr std::size_t compute_salient_members_count_v= compute_salient_members_count_impl< T >();
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< compute_salient_members_count_v< std::decay_t< Aggregate > > >( std::forward< Aggregate >( agg ) );
}
template< typename Aggregate >
using aggregate_tuple_t= decay_tuple_t< std::decay_t< decltype( tuplizeAggregate( std::declval< const Aggregate & >() ) ) > >;
}
template< typename T >
constexpr std::size_t
compute_salient_members_count_impl()
{
if constexpr( SalientMembers< T > ) return T::salient_members;
else return aggregate_member_count_v< std::decay_t< T > >;
}
namespace exports::tuplize_aggregate
template< typename T >
constexpr std::size_t compute_salient_members_count_v= compute_salient_members_count_impl< T >();
namespace exports
{
using namespace detail::tuplize_aggregate::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< compute_salient_members_count_v< std::decay_t< Aggregate > > >( std::forward< Aggregate >( agg ) );
}
template< typename Aggregate >
using aggregate_tuple_t= decay_tuple_t< std::decay_t< decltype( tuplizeAggregate( std::declval< const Aggregate & >() ) ) > >;
}
}
namespace Alepha::Hydrogen::Reflection::inline exports::inline tuplizeAggregate_m
{
using namespace detail::tuplizeAggregate_m::exports;
}

View File

@ -56,7 +56,7 @@ namespace
static_assert( Alepha::Reflection::aggregate_empty_bases_v< instance3 > == 2 );
auto t= "test"_test <=[]
{
using namespace Alepha::Reflection::detail::aggregate_members;
using namespace Alepha::Reflection::detail::aggregate_members_m;
std::cout << Alepha::Reflection::aggregate_empty_bases_v< instance3 > << std::endl;
static_assert( std::is_empty_v< empty1 > );