From ad053e91750fd081ed2dc998b4cabf95e9907201 Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Sat, 13 Jul 2024 05:32:57 -0400 Subject: [PATCH 1/3] Negation support for binary `Enum` --- Enum.h | 9 +++++++++ Enum.test/0.cc | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/Enum.h b/Enum.h index ac3d31a..584c482 100644 --- a/Enum.h +++ b/Enum.h @@ -224,6 +224,15 @@ namespace Alepha::Hydrogen ::detail:: Enum_m } }; + template< EnumValueString no, EnumValueString yes > + constexpr Enum< no, yes > + operator not ( Enum< no, yes > enumeration ) + { + enumeration.set_index( 1 - enumeration.get_index() ); + + return enumeration; + } + namespace exports { template< auto ... values > diff --git a/Enum.test/0.cc b/Enum.test/0.cc index a327809..f4f0a0f 100644 --- a/Enum.test/0.cc +++ b/Enum.test/0.cc @@ -9,6 +9,16 @@ static_assert( __cplusplus > 2020'99 ); #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; @@ -58,4 +68,21 @@ static auto init= Alepha::Utility::enroll <=[] { { "Wrong", { "Wrong" }, { "Adam", "Baker", "Charlie", "David" } }, }; + + static_assert( not CanNegate< Alepha::Enum< "one"_value > > ); + static_assert( CanNegate< Alepha::Enum< "one"_value, "two"_value > > ); + static_assert( not CanNegate< Alepha::Enum< "one"_value, "two"_value, "three"_value > > ); + + "Does negation of a two form enum work?"_test <=TableTest + < + []( const Alepha::Enum< "No"_value, "Yes"_value > e ) + { + return not e; + } + > + ::Cases + { + { "No -> Yes", { "No"_value }, "Yes"_value }, + { "Yes -> No", { "Yes"_value }, "No"_value }, + }; }; From 3c814a53c372643a47e8fe715f93d067b2aeed6a Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Sat, 13 Jul 2024 16:37:25 -0400 Subject: [PATCH 2/3] Access the enumerates as constexpr string array --- Enum.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Enum.h b/Enum.h index 584c482..ee9e58e 100644 --- a/Enum.h +++ b/Enum.h @@ -120,7 +120,31 @@ namespace Alepha::Hydrogen ::detail:: Enum_m StrictInteger value= static_cast< StrictInteger >( 0 ); + struct StringArray + { + static constexpr std::size_t size() noexcept { return sizeof...( values ); } + + static constexpr const ConstexprString array[ StringArray::size() ]= { ( values.cs_string() )... }; + + constexpr const ConstexprString & + operator[] ( const std::size_t sz ) const + { + return array[ sz ]; + } + + constexpr const ConstexprString *begin() const { return array; } + constexpr const ConstexprString *end() const { return &array[ size() ]; } + }; + public: + static constexpr StringArray keys() { return {}; } + + constexpr const ConstexprString & + key() const + { + return keys()[ static_cast< std::size_t >( value ) ]; + } + static constexpr std::string name() { return buildAllNames( { ( values.cs_string() )... } ); } static constexpr bool accepts( const std::string &s ) { return ( ... || ( values.cs_string().c_str() == s ) ); } From 308b64fc643ece448ace4cc8f805b0a9ecff1108 Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Sat, 13 Jul 2024 16:38:06 -0400 Subject: [PATCH 3/3] `UnifiedEnum` -- union construction of enumerates --- CMakeLists.txt | 1 + UnifiedEnum.h | 129 ++++++++++++++++++++++++++++++++ UnifiedEnum.test/0.cc | 92 +++++++++++++++++++++++ UnifiedEnum.test/CMakeLists.txt | 1 + 4 files changed, 223 insertions(+) create mode 100644 UnifiedEnum.h create mode 100644 UnifiedEnum.test/0.cc create mode 100644 UnifiedEnum.test/CMakeLists.txt 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 )