diff --git a/CMakeLists.txt b/CMakeLists.txt index cd8d722..0208302 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ add_subdirectory( Utility ) # The local subdir tests to build add_subdirectory( AutoRAII.test ) add_subdirectory( Enum.test ) +add_subdirectory( UnifiedEnum.test ) add_subdirectory( make_template.test ) add_subdirectory( comparisons.test ) add_subdirectory( Exception.test ) diff --git a/UnifiedEnum.h b/UnifiedEnum.h new file mode 100644 index 0000000..94f1a53 --- /dev/null +++ b/UnifiedEnum.h @@ -0,0 +1,129 @@ +static_assert( __cplusplus > 2023'00 ); + +#pragma once + +#include + +#include "Enum.h" + +namespace Alepha::Hydrogen ::detail:: UnifiedEnum_m +{ + inline namespace exports {} + + template< typename T > + constexpr bool is_alepha_enum_v= false; + + using detail::Enum_m::EnumValueString; + + template< detail::Enum_m::EnumValueString ... values > + constexpr bool is_alepha_enum_v< Enum< values... > >{ true }; + + template< typename T > + concept EnhancedEnumerate= is_alepha_enum_v< T >; + + namespace exports + { + template< EnhancedEnumerate ... > + class UnifiedEnum; + } + + template< EnumValueString cs_strs > + struct StringWrapper; + + template< EnhancedEnumerate ... enums > + struct unify_values; + + template< detail::Enum_m::EnumValueString ... cs_str > + struct unify_values< Enum< cs_str... > > + { + using type= TypeList< StringWrapper< cs_str >... >; + }; + + template< detail::Enum_m::EnumValueString ... cs_str, EnhancedEnumerate ... next > + struct unify_values< Enum< cs_str... >, next... > + { + using type= list_cat_t< TypeList< StringWrapper< cs_str >... >, + typename unify_values< next... >::type >; + }; + + // Because of the way that this works, I do wind up with a possibility of duplicates + // in the enumerate list. This is (hopefully) benign. The underlying `Enum` + // type doesn't check for duplicates for the same reason we'll not check here. + // It's hopefully benign as the duplicated enumerate values simply become an + // unused index by the `get_index` and `set_index` functions. For the most + // part users of the class shouldn't be calling those functions. If they are, + // it should merely be for serialization. As such, skipping a value in the + // sequential indexing shouldn't be observable. + // + // Without good enough support for a compiletime search and dedupe on types, + // we have to resort to O( N ** 2 ) or worse algorithms to remove duplicates. + // With so much compiletime algorithm pressure already, this would become a + // significant pain point for TUs which have a lot of `Enum`s. + // + // An unfortunate side-effect of this design choice is that when/if this design + // is changed to support deduplication of enumerate values (strings), that + // will create a massive but subtle ABI break: + // `UnifiedEnum< Enum< "a"_value >, Enum< "a"_value > >` would now use a + // different index scheme, despite having the same typename. + template< TypeListType > struct build_enum; + + template< detail::Enum_m::EnumValueString ... cs_str > + struct build_enum< TypeList< StringWrapper< cs_str >... > > + { + using type= Enum< cs_str... >; + }; + + template< EnhancedEnumerate ... EnumType > + class exports::UnifiedEnum + : protected build_enum< typename unify_values< EnumType... >::type >::type + { + private: + using Base= typename build_enum< typename unify_values< EnumType... >::type >::type; + + public: + using Base::Base; + using Base::name; + using Base::accepts; + using Base::set_index; + using Base::get_index; + + template< typename Enumerate > + requires( list_contains_v< TypeList< EnumType... >, Enumerate > ) + UnifiedEnum( const Enumerate e ) + { + unsigned value= 0; + for( const auto &next: this->keys() ) + { + if( next == e.key() ) + { + set_index( value ); + return; + } + ++value; + } + + // It should not be possible to reach here. + abort(); + } + + friend std::ostream & + operator << ( std::ostream &os, const UnifiedEnum &rhs ) + { + return os << static_cast< const Base & >( rhs ); + } + + friend std::istream & + operator >> ( std::istream &is, UnifiedEnum &rhs ) + { + return is >> static_cast< Base & >( rhs ); + } + + friend constexpr bool + operator == ( const UnifiedEnum &, const UnifiedEnum & )= default; + }; +} + +namespace Alepha::Hydrogen::inline exports::inline UnifiedEnum_m +{ + using namespace detail::UnifiedEnum_m::exports; +} diff --git a/UnifiedEnum.test/0.cc b/UnifiedEnum.test/0.cc new file mode 100644 index 0000000..36d2e37 --- /dev/null +++ b/UnifiedEnum.test/0.cc @@ -0,0 +1,92 @@ +static_assert( __cplusplus > 2023'00 ); + +#include "../UnifiedEnum.h" + +#include +#include + +#include + +#include + +namespace +{ + template< typename T > + concept CanNegate= + requires( const T &t ) + { + { not t }; + }; +} + +static auto init= Alepha::Utility::enroll <=[] +{ + using namespace Alepha::literals::enum_literals; + using namespace Alepha::Testing::literals; + + using namespace Alepha::Testing::exports; + + using BirdMeat= Alepha::Enum< "Chicken"_value, "Duck"_value, "Quail"_value >; + using LandMeat= Alepha::Enum< "Beef"_value, "Chicken"_value, "Pork"_value >; + + using MyEnum= Alepha::UnifiedEnum< BirdMeat, LandMeat >; + + MyEnum e; + + "Can Unified Enum be constructed from a base enum (left base)?"_test <=TableTest + < + []( const BirdMeat &bm ) -> MyEnum + { + return MyEnum( bm ); + } + > + ::Cases + { + { "Chicken", { "Chicken"_value }, "Chicken"_value }, + { "Duck", { "Duck"_value }, "Duck"_value }, + { "Quail", { "Quail"_value }, "Quail"_value }, + }; + + "Unified Enum round trip printing test"_test <= TableTest + < + []( const std::string s ) -> std::string + { + MyEnum e; + std::istringstream iss{ s }; + + ( iss >> e ); + return Alepha::IOStreams::String{} << e; + } + > + ::Cases + { + { "Beef", { "Beef" }, "Beef" }, + { "Chicken", { "Chicken" }, "Chicken" }, + { "Duck", { "Duck" }, "Duck" }, + { "Unicorn", { "Unicorn" }, std::type_identity< Alepha::EnumTextMismatchError >{} }, + }; + + "Unified Enum failure parse"_test <= TableTest + < + []( const std::string s ) + { + try + { + MyEnum e; + std::istringstream iss{ s }; + + ( iss >> e ); + throw "Fail"; + } + catch( const Alepha::EnumTextMismatchError &ex ) + { + return ex.expectedValues(); + } + } + > + ::Cases + { + { "Unicorn", { "Unicorn" }, { "Chicken", "Duck", "Quail", "Beef", "Chicken", "Pork" } }, + }; +}; + diff --git a/UnifiedEnum.test/CMakeLists.txt b/UnifiedEnum.test/CMakeLists.txt new file mode 100644 index 0000000..b099603 --- /dev/null +++ b/UnifiedEnum.test/CMakeLists.txt @@ -0,0 +1 @@ +unit_test( 0 )