mirror of
https://github.com/aantron/better-enums.git
synced 2025-12-06 16:56:42 +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 {
|
||||
|
||||
// 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.
|
||||
|
||||
// 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)
|
||||
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)
|
||||
if(ENUM_CLASS_INDEX EQUAL -1)
|
||||
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)
|
||||
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
|
||||
# support the requested configuration, write a message and generate a no-op
|
||||
@ -66,6 +82,14 @@ elseif(CONFIGURATION STREQUAL STRICT_CONVERSION)
|
||||
endif()
|
||||
elseif(CONFIGURATION STREQUAL CXX98)
|
||||
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()
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
endif()
|
||||
@ -87,12 +111,12 @@ endforeach(TEST)
|
||||
# Select examples to build.
|
||||
|
||||
set(EXAMPLES
|
||||
1-hello-world 2-conversions 3-iterate 4-switch 5-iostreams 6-safety
|
||||
7-representation 8-constexpr 101-special-values 102-any-underlying
|
||||
1-hello-world 2-conversions 3-iterate 4-switch 6-iostreams 7-safety
|
||||
8-representation 9-constexpr 101-special-values 102-any-underlying
|
||||
103-bitset 104-quine 105-c++17-reflection)
|
||||
|
||||
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)
|
||||
|
||||
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})
|
||||
endif()
|
||||
|
||||
if(CONFIGURATION STREQUAL CXX14)
|
||||
set(EXAMPLES 5-map)
|
||||
endif()
|
||||
|
||||
foreach(EXAMPLE ${EXAMPLES})
|
||||
add_executable(example-${EXAMPLE} ../example/${EXAMPLE}.cc)
|
||||
endforeach(EXAMPLE)
|
||||
|
||||
@ -125,6 +125,9 @@ all-configurations :
|
||||
make TITLE=$(TITLE)-enum-class \
|
||||
CMAKE_OPTIONS="$(CMAKE_OPTIONS) -DCONFIGURATION=STRICT_CONVERSION" \
|
||||
one-configuration
|
||||
make TITLE=$(TITLE)-c++14 \
|
||||
CMAKE_OPTIONS="$(CMAKE_OPTIONS) -DCONFIGURATION=CXX14" \
|
||||
one-configuration
|
||||
make TITLE=$(TITLE)-c++98 \
|
||||
CMAKE_OPTIONS="$(CMAKE_OPTIONS) -DCONFIGURATION=CXX98" \
|
||||
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