mirror of
https://github.com/aantron/better-enums.git
synced 2025-12-07 09:16:44 +08:00
Bidirectional maps between enums and any types.
Also added a C++14 configuration to testing, but using it only for bidrectional map testing at the moment.
This commit is contained in:
parent
0f816be0cd
commit
eac6afacdc
77
doc/tutorial/5-map.md
Normal file
77
doc/tutorial/5-map.md
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
## Maps
|
||||||
|
|
||||||
|
It is possible to create `constexpr` bidirectional maps between Better Enums and
|
||||||
|
any type. This is currently an experimental feature. Feedback is very much
|
||||||
|
wanted, but please don't build any mission-critical code on top of this :)
|
||||||
|
|
||||||
|
The way it works is you give Better Enums a function — say,
|
||||||
|
`const char* describe(Channel)`. The library enumerates it to make a map.
|
||||||
|
|
||||||
|
The reason for using a function is that a `switch` statement is, I believe, the
|
||||||
|
only place where a compiler will check for exhaustiveness. If you forget to
|
||||||
|
create a case for one of the enum's constants, the compiler can let you know.
|
||||||
|
Obviously, a `switch` statement is not data, and needs to be inside a function.
|
||||||
|
It can only be inside a `constexpr` function in $cxx14, so this feature is most
|
||||||
|
natural in $cxx14. When you pass the function to Better Enums, the library can
|
||||||
|
build up a lookup data structure at compile time.
|
||||||
|
|
||||||
|
Actually, right now, Better Enums doesn't quite do that — it enumerates
|
||||||
|
the function *every* time you want to convert to an enum (but not *from* an
|
||||||
|
enum). It simply does a linear scan every time. This is because I haven't yet
|
||||||
|
found a data structure whose compile-time generation is fast enough for
|
||||||
|
practical use.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
<em>#include</em> <<em>enum.h</em>>
|
||||||
|
|
||||||
|
<em>ENUM</em>(<em>Channel</em>, <em>int</em>, <em>Red</em>, <em>Green</em>, <em>Blue</em>)
|
||||||
|
|
||||||
|
We will create a map from this function:
|
||||||
|
|
||||||
|
constexpr <em>const char* describe</em>(<em>Channel channel</em>)
|
||||||
|
{
|
||||||
|
switch(<em>channel</em>) {
|
||||||
|
case <em>Channel::Red</em>: return <em>"the red channel"</em>;
|
||||||
|
case <em>Channel::Green</em>: return <em>"the green channel"</em>;
|
||||||
|
case <em>Channel::Blue</em>: return <em>"the blue channel"</em>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "needed for gcc 5.1";
|
||||||
|
}
|
||||||
|
|
||||||
|
Here is the map. The actual type is `better_enums::map<Channel, const char*>`.
|
||||||
|
|
||||||
|
constexpr auto <em>descriptions</em> = <em>better_enums::make_map</em>(<em>describe</em>);
|
||||||
|
|
||||||
|
And the usage:
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
std::cout << <em>descriptions[Channel::Red]</em> << std::endl;
|
||||||
|
|
||||||
|
std::cout << <em>descriptions</em>.<em>from_enum</em>(<em>Channel::Red</em>) << std::endl;
|
||||||
|
std::cout << <em>descriptions</em>.<em>to_enum</em>(<em>"the green channel"</em>) << std::endl;
|
||||||
|
|
||||||
|
auto <em>not_a_literal</em> = <em>std::string</em>(<em>"the blue channel"</em>);
|
||||||
|
std::cout << <em>descriptions</em>.<em>to_enum</em>(<em>not_a_literal</em>.<em>c_str()</em>) << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`make_map` above produces a value of type `better_enums::map<E, T>`. The full
|
||||||
|
signature of the template `better_enums::map` is
|
||||||
|
|
||||||
|
~~~comment
|
||||||
|
<em>template <typename Enum, typename T, typename Compare = map_compare<T>></em>
|
||||||
|
~~~
|
||||||
|
|
||||||
|
`Compare` has to be a class with a static member function
|
||||||
|
`bool less(const T&, const T&)`. The default implementation
|
||||||
|
`better_enums::map_compare` simply applies `operator <`, except when `T` is
|
||||||
|
`const char*`. In that case, it does lexicographic comparison.
|
||||||
|
|
||||||
|
%% description = Mapping enums to arbitrary types and vice versa.
|
||||||
63
enum.h
63
enum.h
@ -1190,6 +1190,69 @@ BETTER_ENUMS__CONSTEXPR inline bool operator >=(const Enum &a, const Enum &b) \
|
|||||||
|
|
||||||
namespace better_enums {
|
namespace better_enums {
|
||||||
|
|
||||||
|
// Maps.
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct map_compare {
|
||||||
|
BETTER_ENUMS__CONSTEXPR static bool less(const T& a, const T& b)
|
||||||
|
{ return a < b; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct map_compare<const char*> {
|
||||||
|
BETTER_ENUMS__CONSTEXPR static bool less(const char *a, const char *b)
|
||||||
|
{ return less_loop(a, b); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
BETTER_ENUMS__CONSTEXPR static bool
|
||||||
|
less_loop(const char *a, const char *b, size_t index = 0)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
a[index] != b[index] ? a[index] < b[index] :
|
||||||
|
a[index] == '\0' ? false :
|
||||||
|
less_loop(a, b, index + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Enum, typename T, typename Compare = map_compare<T> >
|
||||||
|
struct map {
|
||||||
|
typedef T (*function)(Enum);
|
||||||
|
|
||||||
|
BETTER_ENUMS__CONSTEXPR explicit map(function f) : _f(f) { }
|
||||||
|
|
||||||
|
BETTER_ENUMS__CONSTEXPR T from_enum(Enum value) const { return _f(value); }
|
||||||
|
BETTER_ENUMS__CONSTEXPR T operator [](Enum value) const
|
||||||
|
{ return _f(value); }
|
||||||
|
|
||||||
|
BETTER_ENUMS__CONSTEXPR Enum to_enum(T value) const
|
||||||
|
{
|
||||||
|
return
|
||||||
|
_or_throw(to_enum_nothrow(value), "map::to_enum: invalid argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
BETTER_ENUMS__CONSTEXPR optional<Enum>
|
||||||
|
to_enum_nothrow(T value, size_t index = 0) const
|
||||||
|
{
|
||||||
|
return
|
||||||
|
index >= Enum::_size() ? optional<Enum>() :
|
||||||
|
Compare::less(_f(Enum::_values()[index]), value) ||
|
||||||
|
Compare::less(value, _f(Enum::_values()[index])) ?
|
||||||
|
to_enum_nothrow(value, index + 1) :
|
||||||
|
Enum::_values()[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const function _f;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Enum, typename T>
|
||||||
|
BETTER_ENUMS__CONSTEXPR map<Enum, T> make_map(T (*f)(Enum))
|
||||||
|
{
|
||||||
|
return map<Enum, T>(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Stream I/O operators.
|
// Stream I/O operators.
|
||||||
|
|
||||||
// This template is used as a sort of enable_if for SFINAE. It should be
|
// This template is used as a sort of enable_if for SFINAE. It should be
|
||||||
|
|||||||
74
example/5-map.cc
Normal file
74
example/5-map.cc
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// This file was generated automatically.
|
||||||
|
|
||||||
|
// Maps
|
||||||
|
//
|
||||||
|
// It is possible to create constexpr bidirectional maps between Better Enums
|
||||||
|
// and any type. This is currently an experimental feature. Feedback is very
|
||||||
|
// much wanted, but please don't build any mission-critical code on top of this
|
||||||
|
// :)
|
||||||
|
//
|
||||||
|
// The way it works is you give Better Enums a function - say, const char*
|
||||||
|
// describe(Channel). The library enumerates it to make a map.
|
||||||
|
//
|
||||||
|
// The reason for using a function is that a switch statement is, I believe, the
|
||||||
|
// only place where a compiler will check for exhaustiveness. If you forget to
|
||||||
|
// create a case for one of the enum's constants, the compiler can let you know.
|
||||||
|
// Obviously, a switch statement is not data, and needs to be inside a function.
|
||||||
|
// It can only be inside a constexpr function in C++14, so this feature is most
|
||||||
|
// natural in C++14. When you pass the function to Better Enums, the library can
|
||||||
|
// build up a lookup data structure at compile time.
|
||||||
|
//
|
||||||
|
// Actually, right now, Better Enums doesn't quite do that - it enumerates the
|
||||||
|
// function every time you want to convert to an enum (but not from an enum). It
|
||||||
|
// simply does a linear scan every time. This is because I haven't yet found a
|
||||||
|
// data structure whose compile-time generation is fast enough for practical
|
||||||
|
// use.
|
||||||
|
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <enum.h>
|
||||||
|
|
||||||
|
ENUM(Channel, int, Red, Green, Blue)
|
||||||
|
|
||||||
|
// We will create a map from this function:
|
||||||
|
|
||||||
|
constexpr const char* describe(Channel channel)
|
||||||
|
{
|
||||||
|
switch(channel) {
|
||||||
|
case Channel::Red: return "the red channel";
|
||||||
|
case Channel::Green: return "the green channel";
|
||||||
|
case Channel::Blue: return "the blue channel";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "for gcc 5.1";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here is the map. The actual type is better_enums::map<Channel, const char*>.
|
||||||
|
|
||||||
|
constexpr auto descriptions = better_enums::make_map(describe);
|
||||||
|
|
||||||
|
// And the usage:
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
std::cout << descriptions[Channel::Red] << std::endl;
|
||||||
|
|
||||||
|
std::cout << descriptions.from_enum(Channel::Red) << std::endl;
|
||||||
|
std::cout << descriptions.to_enum("the green channel") << std::endl;
|
||||||
|
|
||||||
|
auto not_a_literal = std::string("the blue channel");
|
||||||
|
std::cout << descriptions.to_enum(not_a_literal.c_str()) << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// make_map above produces a value of type better_enums::map<E, T>. The full
|
||||||
|
// signature of the template better_enums::map is
|
||||||
|
//
|
||||||
|
// template <typename Enum, typename T, typename Compare = map_compare<T>>
|
||||||
|
//
|
||||||
|
// Compare has to be a class with a static member function bool less(const T&,
|
||||||
|
// const T&). The default implementation better_enums::map_compare simply
|
||||||
|
// applies operator <, except when T is const char*. In that case, it does
|
||||||
|
// lexicographic comparison.
|
||||||
@ -14,6 +14,14 @@ else()
|
|||||||
set(SUPPORTS_CONSTEXPR 1)
|
set(SUPPORTS_CONSTEXPR 1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_relaxed_constexpr
|
||||||
|
RELAXED_CONSTEXPR_INDEX)
|
||||||
|
if(RELAXED_CONSTEXPR_INDEX EQUAL -1)
|
||||||
|
set(SUPPORTS_RELAXED_CONSTEXPR 0)
|
||||||
|
else()
|
||||||
|
set(SUPPORTS_RELAXED_CONSTEXPR 1)
|
||||||
|
endif()
|
||||||
|
|
||||||
list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_strong_enums ENUM_CLASS_INDEX)
|
list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_strong_enums ENUM_CLASS_INDEX)
|
||||||
if(ENUM_CLASS_INDEX EQUAL -1)
|
if(ENUM_CLASS_INDEX EQUAL -1)
|
||||||
set(SUPPORTS_ENUM_CLASS 0)
|
set(SUPPORTS_ENUM_CLASS 0)
|
||||||
@ -28,6 +36,14 @@ if(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.7)
|
|||||||
set(SUPPORTS_ENUM_CLASS 0)
|
set(SUPPORTS_ENUM_CLASS 0)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Not supporting C++14 testing on clang++34 due to buggy library installed in
|
||||||
|
# Travis Ubuntu image.
|
||||||
|
|
||||||
|
if(CMAKE_CXX_COMPILER_ID STREQUAL Clang
|
||||||
|
AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.5)
|
||||||
|
set(SUPPORTS_RELAXED_CONSTEXPR 0)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
# Select standard based on the requested configuration. If the compiler does not
|
# Select standard based on the requested configuration. If the compiler does not
|
||||||
# support the requested configuration, write a message and generate a no-op
|
# support the requested configuration, write a message and generate a no-op
|
||||||
@ -66,6 +82,14 @@ elseif(CONFIGURATION STREQUAL STRICT_CONVERSION)
|
|||||||
endif()
|
endif()
|
||||||
elseif(CONFIGURATION STREQUAL CXX98)
|
elseif(CONFIGURATION STREQUAL CXX98)
|
||||||
set(CMAKE_CXX_STANDARD 98)
|
set(CMAKE_CXX_STANDARD 98)
|
||||||
|
elseif(CONFIGURATION STREQUAL CXX14)
|
||||||
|
if(SUPPORTS_RELAXED_CONSTEXPR)
|
||||||
|
set(CMAKE_CXX_STANDARD 14)
|
||||||
|
else()
|
||||||
|
message(WARNING "This compiler does not support relaxed constexpr")
|
||||||
|
file(WRITE "${DO_NOT_TEST_FILE}")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
else()
|
else()
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
endif()
|
endif()
|
||||||
@ -87,12 +111,12 @@ endforeach(TEST)
|
|||||||
# Select examples to build.
|
# Select examples to build.
|
||||||
|
|
||||||
set(EXAMPLES
|
set(EXAMPLES
|
||||||
1-hello-world 2-conversions 3-iterate 4-switch 5-iostreams 6-safety
|
1-hello-world 2-conversions 3-iterate 4-switch 6-iostreams 7-safety
|
||||||
7-representation 8-constexpr 101-special-values 102-any-underlying
|
8-representation 9-constexpr 101-special-values 102-any-underlying
|
||||||
103-bitset 104-quine 105-c++17-reflection)
|
103-bitset 104-quine 105-c++17-reflection)
|
||||||
|
|
||||||
set(SKIPPED_FOR_CXX98
|
set(SKIPPED_FOR_CXX98
|
||||||
8-constexpr 101-special-values 102-any-underlying 103-bitset 104-quine
|
5-map 9-constexpr 101-special-values 102-any-underlying 103-bitset 104-quine
|
||||||
105-c++17-reflection)
|
105-c++17-reflection)
|
||||||
|
|
||||||
set(SKIPPED_FOR_STRICT_CONVERSION 4-switch 102-any-underlying)
|
set(SKIPPED_FOR_STRICT_CONVERSION 4-switch 102-any-underlying)
|
||||||
@ -105,6 +129,10 @@ if(CONFIGURATION STREQUAL STRICT_CONVERSION)
|
|||||||
list(REMOVE_ITEM EXAMPLES ${SKIPPED_FOR_STRICT_CONVERSION})
|
list(REMOVE_ITEM EXAMPLES ${SKIPPED_FOR_STRICT_CONVERSION})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(CONFIGURATION STREQUAL CXX14)
|
||||||
|
set(EXAMPLES 5-map)
|
||||||
|
endif()
|
||||||
|
|
||||||
foreach(EXAMPLE ${EXAMPLES})
|
foreach(EXAMPLE ${EXAMPLES})
|
||||||
add_executable(example-${EXAMPLE} ../example/${EXAMPLE}.cc)
|
add_executable(example-${EXAMPLE} ../example/${EXAMPLE}.cc)
|
||||||
endforeach(EXAMPLE)
|
endforeach(EXAMPLE)
|
||||||
|
|||||||
@ -125,6 +125,9 @@ all-configurations :
|
|||||||
make TITLE=$(TITLE)-enum-class \
|
make TITLE=$(TITLE)-enum-class \
|
||||||
CMAKE_OPTIONS="$(CMAKE_OPTIONS) -DCONFIGURATION=STRICT_CONVERSION" \
|
CMAKE_OPTIONS="$(CMAKE_OPTIONS) -DCONFIGURATION=STRICT_CONVERSION" \
|
||||||
one-configuration
|
one-configuration
|
||||||
|
make TITLE=$(TITLE)-c++14 \
|
||||||
|
CMAKE_OPTIONS="$(CMAKE_OPTIONS) -DCONFIGURATION=CXX14" \
|
||||||
|
one-configuration
|
||||||
make TITLE=$(TITLE)-c++98 \
|
make TITLE=$(TITLE)-c++98 \
|
||||||
CMAKE_OPTIONS="$(CMAKE_OPTIONS) -DCONFIGURATION=CXX98" \
|
CMAKE_OPTIONS="$(CMAKE_OPTIONS) -DCONFIGURATION=CXX98" \
|
||||||
one-configuration
|
one-configuration
|
||||||
|
|||||||
4
test/expect/5-map
Normal file
4
test/expect/5-map
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
the red channel
|
||||||
|
the red channel
|
||||||
|
Green
|
||||||
|
Blue
|
||||||
Loading…
x
Reference in New Issue
Block a user