diff --git a/Reflection/CMakeLists.txt b/Reflection/CMakeLists.txt index d124a33..493c869 100644 --- a/Reflection/CMakeLists.txt +++ b/Reflection/CMakeLists.txt @@ -1 +1,4 @@ add_subdirectory( tuplizeAggregate.test ) +add_subdirectory( has_tagged_ctor.test ) +add_subdirectory( tagged_ctor_count.test ) +add_subdirectory( tagged_ctor_arg.test ) diff --git a/Reflection/detail/config.h b/Reflection/detail/config.h index 3fb0a99..cf82900 100644 --- a/Reflection/detail/config.h +++ b/Reflection/detail/config.h @@ -6,3 +6,12 @@ static_assert( __cplusplus > 2020'99 ); // This file will eventually contain all of the constants that control how much generated code the // C++17 Reflection system will geeerate. + +// Note: not exported! +namespace Alepha::Hydrogen::Reflection ::detail:: config_m +{ + namespace C + { + const std::size_t max_ctor_size= 32; + } +} diff --git a/Reflection/has_tagged_ctor.h b/Reflection/has_tagged_ctor.h new file mode 100644 index 0000000..5fcce43 --- /dev/null +++ b/Reflection/has_tagged_ctor.h @@ -0,0 +1,95 @@ +static_assert( __cplusplus > 2020'99 ); + +#pragma once + +#include + +#include +#include +#include + +#include + +#include + +namespace Alepha::Hydrogen::Reflection ::detail:: has_tagged_ctor_m +{ + inline namespace exports {} + + namespace C + { + using namespace Reflection::detail::config_m::C; + } + + // Basic methodology here: I probe the number of arguments that an object can be constructed with, given the tag. + // I don't care what they actually are, as there's no easy way to hoist out what they are. However, for an + // object ctor, we have to get the exact right number. + // Therefore we just wind up finding the smallest-ordinality ctor with that tag. + + // The basic adaptable argument. Because it pretends to be anything, it can be used as a parameter in invoking + // any initialization method. + template< typename Forbidden > + struct argument + { + template< typename T > + requires( not Concepts::SameAs< T, Forbidden > ) + constexpr operator T (); + }; + + template< typename T, typename ... Args > + requires Concepts::ConstructibleFrom< T, Args... > + constexpr void construct( const std::tuple< Args... > & ); + + template< typename T, typename Tuple > + concept ConstructibleWithTuple= + requires( Tuple tup ) + { + { construct< T >( tup ) }; + }; + + template< typename Forbidden, typename Seq > + struct expand; + + template< typename Forbidden, std::size_t ... Ints > + struct expand< Forbidden, std::index_sequence< Ints... > > + { + using type= decltype( std::make_tuple( ( Ints, argument< Forbidden >{} )... ) ); + }; + + template< typename Forbidden, std::size_t argcnt > + using expand_t= typename expand< Forbidden, std::make_index_sequence< argcnt > >::type; + + template< typename T, typename tag, std::size_t args > + concept ConstructibleWith= + ConstructibleWithTuple< T, decltype( std::tuple_cat( std::declval< expand_t< T, args > >(), std::declval< std::tuple< tag > >() ) ) >; + + // 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 tag, std::size_t cnt= 0 > + struct has_tagged_ctor + : has_tagged_ctor< T, tag, cnt + 1 > {}; + + template< typename T, typename tag > + struct has_tagged_ctor< T, tag, C::max_ctor_size > + : std::false_type {}; + + template< typename T, typename tag, std::size_t depth > + requires ConstructibleWith< T, tag, depth > + struct has_tagged_ctor< T, tag, depth > + : std::true_type {}; + + namespace exports + { + template< typename T, typename tag > + constexpr bool has_tagged_ctor_v= has_tagged_ctor< T, tag >::value; + + template< typename T, typename tag > + struct has_tagged_ctor : std::bool_constant< has_tagged_ctor_v< T, tag > > {}; + } +} + +namespace Alepha::Hydrogen::Reflection::inline exports::inline has_tagged_ctor_m +{ + using namespace detail::has_tagged_ctor_m::exports; +} diff --git a/Reflection/has_tagged_ctor.test/0.cc b/Reflection/has_tagged_ctor.test/0.cc new file mode 100644 index 0000000..ffea6cd --- /dev/null +++ b/Reflection/has_tagged_ctor.test/0.cc @@ -0,0 +1,23 @@ +static_assert( __cplusplus > 2020'99 ); + +#include + +#include +#include +#include + +namespace +{ + using namespace Alepha::Testing::literals; + + struct tag {}; + struct tag2 {}; + struct instance + { + explicit instance( int a, float b, char c, double d, tag t ); + }; + + using namespace Alepha::Reflection::exports::has_tagged_ctor_m; + static_assert( has_tagged_ctor_v< instance, tag > ); + static_assert( not has_tagged_ctor_v< instance, tag2 > ); +} diff --git a/Reflection/has_tagged_ctor.test/CMakeLists.txt b/Reflection/has_tagged_ctor.test/CMakeLists.txt new file mode 100644 index 0000000..b099603 --- /dev/null +++ b/Reflection/has_tagged_ctor.test/CMakeLists.txt @@ -0,0 +1 @@ +unit_test( 0 ) diff --git a/Reflection/tagged_ctor_arg.h b/Reflection/tagged_ctor_arg.h new file mode 100644 index 0000000..d1f2283 --- /dev/null +++ b/Reflection/tagged_ctor_arg.h @@ -0,0 +1,269 @@ +static_assert( __cplusplus > 2020'99 ); + +#pragma once + +#include + +#include +#include +#include + +#include +#include + +#include + +#include + +#include +#include + +namespace Alepha::Hydrogen::Reflection ::detail:: tagged_ctor_arg_m +{ + inline namespace exports {} + + namespace C + { + using namespace Reflection::detail::config_m::C; + } + + namespace protection + { + //template< typename Fake, typename Dummy > + //inline void get_argument_type( Fake, Dummy ) {} + + template< typename Type, typename tag, std::size_t index > + struct lookup_helper + { + template< typename Dummy, typename > + friend constexpr auto get_argument_type( const lookup_helper< Type, tag, index > &, Dummy ); + }; + + template< typename Type, typename tag, std::size_t index, typename arg_type > + struct argument_extractor + { + template< typename Dummy, typename= std::enable_if_t< std::is_same_v< Dummy, std::nullptr_t > > > + friend constexpr auto + get_argument_type( const lookup_helper< Type, tag, index > &, Dummy ) + { + return std::type_identity< arg_type >(); + } + + static constexpr void member() {} + }; + + template< typename Type, typename tag, std::size_t index > + struct exporting_argument + { + template + < + typename ArgType, + auto= argument_extractor< Type, tag, index, std::decay_t< ArgType > >::member//, + //typename= std::enable_if_t< not std::is_same_v< ArgType, Type > > + > + constexpr operator ArgType () const; + }; + + template< typename Forbidden > + struct argument + { + template< typename T > + requires( not Concepts::SameAs< T, Forbidden > ) + constexpr operator T (); + }; + + template< typename T, typename ... Args > + requires( Concepts::ConstructibleFrom< T, Args... > ) + constexpr void construct( const std::tuple< Args... > & ); + + template< typename T, typename ... Args > + requires( Concepts::ConstructibleFrom< T, Args... > ) + constexpr void construct( std::tuple< Args... > && ); + + template< typename T, typename Tuple > + concept ConstructibleWithTuple= + requires( Tuple tup ) + { + { construct< T >( tup ) }; + }; + + template + < + typename T, + typename tag, + typename sequence= std::index_sequence<>, + typename= void, + typename= void + > + struct extract_all_ctor_arguments + : extract_all_ctor_arguments< T, tag, std::make_index_sequence< sequence::size() + 1 > > {}; + + template + < + typename T, + typename tag + > + struct extract_all_ctor_arguments< T, tag, std::make_index_sequence< C::max_ctor_size >, void > + { + struct impossible; + static_assert( std::is_same_v< impossible, T >, "Max recursion reached." ); + }; + + template + < + typename T, + typename tag, + std::size_t ... positions + > + requires + ( + ConstructibleWithTuple + < + T, + Meta::tuple_cat_t< + tuple_from_list_t< repeat_t< argument< T >, sizeof...( positions ) > >, std::tuple< tag > > + > + ) + struct extract_all_ctor_arguments + < + T, + tag, + std::index_sequence< positions... >, + std::void_t + < + decltype + ( + construct< T >( std::tuple_cat + ( + std::make_tuple( ( positions, std::declval< argument< T > >() )... ), + std::make_tuple( std::declval< tag >() ) + ) ) + ) + > + > + { + static_assert + ( + ConstructibleWithTuple + < + T, + Meta::tuple_cat_t< + tuple_from_list_t< repeat_t< argument< T >, sizeof...( positions ) > >, std::tuple< tag > > + > + ); + + template + < + typename int_const, + typename= std::void_t + < + decltype + ( + construct< T >( std::tuple_cat + ( + std::make_tuple( ( positions, std::declval< exporting_argument< T, tag, positions > >() )... ), + std::make_tuple( std::declval< tag >() ) + ) ) + ) + > + > + struct constant_wrapper + { + // Replay the construction in this body, to cause the friend injection of our + // exporters for detected params to happen only once. If we didn't match, we don't + // want to create false overloads. The standard is murky on which return type + // dominates, in those cases. We are abusing something CWG doesn't like, but they've + // had over a decade to fix it... and some future standard will provide real reflection + // and this hack can vanish. + using type= std::conditional_t + < + true, + typename int_const::type, + // Force instantiation of tag extractor: + decltype( argument_extractor< T, tag, sizeof...( positions ), tag >::member() ) + >; + }; + + using type= typename constant_wrapper< std::integral_constant< std::size_t, 1 + sizeof...( positions ) > >::type; + static constexpr std::size_t value= type::value; + }; + + template + < + typename T, + typename tag, + std::size_t index + > + struct tagged_ctor_extract_impl + { + using type= typename decltype( get_argument_type( + std::declval< lookup_helper< T, tag, index > >(), nullptr ) )::type; + }; + + template + < + typename T, + typename tag, + std::size_t index, + auto fake_type= extract_all_ctor_arguments< T, tag >::value + > + struct tagged_ctor_extract + { + using type= typename tagged_ctor_extract_impl< T, tag, index >::type; + }; + } + + namespace impl + { + template< typename T, typename tag, std::size_t index > + struct tagged_ctor_arg + { + using type= std::decay_t< typename protection::tagged_ctor_extract< T, tag, index >::type >; + }; + + template< typename T, typename tag, std::size_t index > + using tagged_ctor_arg_t= typename tagged_ctor_arg< T, tag, index >::type; + } + + namespace build_impl + { + template< typename T, typename tag, std::size_t size, std::size_t index= 0 > + struct build_ctor_tuple + { + using type= decltype + ( + std::tuple_cat + ( + std::declval< std::tuple< impl::tagged_ctor_arg_t< T, tag, index > > >(), + std::declval< typename build_ctor_tuple< T, tag, size, index + 1 >::type >() + ) + ); + }; + + template< typename T, typename tag, std::size_t size > + struct build_ctor_tuple< T, tag, size, size > + { + using type= std::tuple<>; + }; + + template< typename T, typename tag > + struct build_ctor_tuple_wrapper + { + static_assert( has_tagged_ctor_v< T, tag > ); + inline static constexpr auto sz= tagged_ctor_count_v< T, tag >; + using type= typename build_ctor_tuple< T, tag, sz >::type; + }; + } + + namespace exports + { + template< typename T, typename tag > + using tagged_ctor_args_t= typename build_impl::build_ctor_tuple_wrapper< T, tag >::type; + } +} + +namespace Alepha::Hydrogen::Reflection::inline exports::inline tagged_ctor_arg_m +{ + using namespace detail::tagged_ctor_arg_m::exports; +} + diff --git a/Reflection/tagged_ctor_arg.test/0.cc b/Reflection/tagged_ctor_arg.test/0.cc new file mode 100644 index 0000000..fc61b3e --- /dev/null +++ b/Reflection/tagged_ctor_arg.test/0.cc @@ -0,0 +1,47 @@ +static_assert( __cplusplus > 2020'99 ); + +#include + +#include + +namespace +{ + struct tag {}; + struct tag2 {}; + struct fake {}; + struct instance + { + // There are arity issues with the tagged ctor lookup. + // No other ctor can have the same arity as a tagged ctor. + // (Each tagged ctor has to have a unique arity.) + // + // If they do, it causes "redefinition errors" from template + // friend injection. I believe that the reason for this is + // that each `exporting_argument` object winds up generating + // several `operator T` overloads, one for each object ctor + // that it attempts to convert to. Thus we wind up with + // duplicate bindings. While this is annoying, it's not + // significantly different to the `function_traits` inspector + // in that it requires a single overload. If duplicate + // arity overloads are needed, padding dummy arguments can be + // used are needed. These dummies cannot be defaulted. + // Further, the tag arguments should not be defaulted, as that + // creates pseudo-overloads with the same arity. This advice + // not to default tags applies only when dealing with + // overloads. + // + // The hope is that in a future version of C++ there will + // be a true reflection mechanism for ctors -- and thus that + // facility can be used as a replacement for this reflection + // hack. + explicit instance( int a, float b, char c, double d, tag t ); + explicit instance( long, long, long, long, fake, tag2 t ); + }; + + using namespace Alepha::Reflection::exports::tagged_ctor_arg_m; + using tuple= tagged_ctor_args_t< instance, tag >; + std::tuple< int, float, char, double, tag > t= tuple{}; + static_assert( std::is_same_v< tuple, std::tuple< int, float, char, double, tag > > ); + using tuple2= tagged_ctor_args_t< instance, tag2 >; + static_assert( std::is_same_v< tuple2, std::tuple< long, long, long, long, fake, tag2 > > ); +} diff --git a/Reflection/tagged_ctor_arg.test/CMakeLists.txt b/Reflection/tagged_ctor_arg.test/CMakeLists.txt new file mode 100644 index 0000000..b099603 --- /dev/null +++ b/Reflection/tagged_ctor_arg.test/CMakeLists.txt @@ -0,0 +1 @@ +unit_test( 0 ) diff --git a/Reflection/tagged_ctor_count.h b/Reflection/tagged_ctor_count.h new file mode 100644 index 0000000..13f8663 --- /dev/null +++ b/Reflection/tagged_ctor_count.h @@ -0,0 +1,104 @@ +static_assert( __cplusplus > 2020'99 ); + +#pragma once + +#include + +#include +#include +#include + +#include + +#include + +namespace Alepha::Hydrogen::Reflection ::detail:: tagged_ctor_count_m +{ + inline namespace exports {} + + namespace C + { + using namespace Reflection::detail::config_m::C; + } + + // Basic methodology here: I probe the number of arguments that an object can be constructed with, given the tag. + // I don't care what they actually are, as there's no easy way to hoist out what they are. However, for an + // object ctor, we have to get the exact right number. + // Therefore we just wind up finding the smallest-ordinality ctor with that tag. + + // The basic adaptable argument. Because it pretends to be anything, it can be used as a parameter in invoking + // any initialization method. + template< typename Forbidden > + struct argument + { + template< typename T > + requires( not Concepts::SameAs< T, Forbidden > ) + constexpr operator T (); + }; + + template< typename T, typename ... Args > + requires( Concepts::ConstructibleFrom< T, Args... > ) + constexpr void construct( const std::tuple< Args... > & ); + + template< typename T, typename ... Args > + requires( Concepts::ConstructibleFrom< T, Args... > ) + constexpr void construct( std::tuple< Args... > && ); + + + template< typename T, typename Tuple > + concept ConstructibleWithTuple= + requires( Tuple tup ) + { + { construct< T >( tup ) }; + }; + + template< typename Forbidden, typename Seq > + struct expand; + + template< typename Forbidden, std::size_t ... Ints > + struct expand< Forbidden, std::index_sequence< Ints... > > + { + using type= decltype( std::make_tuple( ( Ints, argument< Forbidden >{} )... ) ); + }; + + template< typename Forbidden, std::size_t argcnt > + using expand_t= typename expand< Forbidden, std::make_index_sequence< argcnt > >::type; + + template< typename T, typename tag, std::size_t args > + concept ConstructibleWith= + ConstructibleWithTuple< T, decltype( std::tuple_cat( std::declval< expand_t< T, args > >(), std::declval< std::tuple< tag > >() ) ) >; + + // 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 tag, std::size_t cnt= 0 > + struct tagged_ctor_count + : tagged_ctor_count< T, tag, cnt + 1 > {}; + + template< typename T, typename tag > + struct tagged_ctor_count< T, tag, C::max_ctor_size + 1 > + { + struct impossible; + static_assert( std::is_same_v< impossible, T >, "Max depth reached." ); + }; + + template< typename T, typename tag, std::size_t depth > + requires( ConstructibleWith< T, tag, depth > ) + struct tagged_ctor_count< T, tag, depth > + // Size is 1 more than the depth we probed, since that also accounts for the tag. + : std::integral_constant< std::size_t, depth + 1 > {}; + + namespace exports + { + template< typename T, typename tag > + constexpr std::size_t tagged_ctor_count_v= tagged_ctor_count< T, tag >::value; + + template< typename T, typename tag > + struct tagged_ctor_count : std::integral_constant< std::size_t, tagged_ctor_count_v< T, tag > > {}; + } +} + +namespace Alepha::Hydrogen::Reflection::inline exports::inline tagged_ctor_count_m +{ + using namespace detail::tagged_ctor_count_m::exports; +} diff --git a/Reflection/tagged_ctor_count.test/0.cc b/Reflection/tagged_ctor_count.test/0.cc new file mode 100644 index 0000000..8ed5d54 --- /dev/null +++ b/Reflection/tagged_ctor_count.test/0.cc @@ -0,0 +1,22 @@ +static_assert( __cplusplus > 2020'99 ); + +#include + +#include +#include +#include + +namespace +{ + using namespace Alepha::Testing::literals; + + struct tag {}; + struct tag2 {}; + struct instance + { + explicit instance( int a, float b, char c, double d, tag t ); + }; + + using namespace Alepha::Reflection::exports::tagged_ctor_count_m; + static_assert( tagged_ctor_count_v< instance, tag > == 5 ); +} diff --git a/Reflection/tagged_ctor_count.test/CMakeLists.txt b/Reflection/tagged_ctor_count.test/CMakeLists.txt new file mode 100644 index 0000000..b099603 --- /dev/null +++ b/Reflection/tagged_ctor_count.test/CMakeLists.txt @@ -0,0 +1 @@ +unit_test( 0 )