From 1e3b3707e281d9ccd5117d29a4a9c18faa70e8f0 Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Thu, 21 Mar 2024 15:31:02 -0400 Subject: [PATCH] Fixed tagged ctor argument inspection code. --- Reflection/CMakeLists.txt | 1 + Reflection/tagged_ctor_arg.h | 234 ++++++++++++++++++ Reflection/tagged_ctor_arg.test/0.cc | 47 ++++ .../tagged_ctor_arg.test/CMakeLists.txt | 1 + 4 files changed, 283 insertions(+) create mode 100644 Reflection/tagged_ctor_arg.h create mode 100644 Reflection/tagged_ctor_arg.test/0.cc create mode 100644 Reflection/tagged_ctor_arg.test/CMakeLists.txt diff --git a/Reflection/CMakeLists.txt b/Reflection/CMakeLists.txt index 2b2b843..493c869 100644 --- a/Reflection/CMakeLists.txt +++ b/Reflection/CMakeLists.txt @@ -1,3 +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/tagged_ctor_arg.h b/Reflection/tagged_ctor_arg.h new file mode 100644 index 0000000..c4b3388 --- /dev/null +++ b/Reflection/tagged_ctor_arg.h @@ -0,0 +1,234 @@ +static_assert( __cplusplus > 2020'99 ); + +#pragma once + +#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, typename= std::enable_if_t< not std::is_same_v< T, Forbidden > > > + constexpr operator T (); + }; + + template< typename T, typename ... Args, typename= std::enable_if_t< std::is_constructible_v< T, Args... > > > + constexpr void construct( std::tuple< Args... > && ); + + 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 + > + 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 >() ) + ) ) + ) + > + > + { + 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 )