mirror of
https://github.com/aantron/better-enums.git
synced 2025-12-06 16:56:42 +08:00
Complete documentation and testing overhaul.
The documentation is now generated from markdown. Samples are generated from the tutorial pages. Testing is done by a Python script which runs the tests for a large number of compilers. This version is not very developer-friendly - the Python scripts need ways of limiting what compilers they try to run. If you don't have 15 compilers installed, you won't be able to run the tests in this commit. Fix coming soon.
This commit is contained in:
parent
a9ddc59808
commit
2acb5743fa
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1 +1,2 @@
|
||||
*.h linguist-language=C++
|
||||
*.tmpl linguist-language=HTML
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,7 @@
|
||||
*.pyc
|
||||
*.exe
|
||||
scratch/
|
||||
doc/html/
|
||||
doc-publish/
|
||||
test/cxxtest/*.cc
|
||||
test/platform/
|
||||
|
||||
76
README.md
76
README.md
@ -1,98 +1,60 @@
|
||||
# Better Enums
|
||||
|
||||
Reflective compile-time C++11 enum library with clean syntax. For example:
|
||||
Reflective compile-time C++ enum library with clean syntax. For example:
|
||||
|
||||
ENUM(Channel, int, Red = 1, Green, Blue);
|
||||
ENUM(Channel, int, Red = 1, Green, Blue)
|
||||
|
||||
defines a type `Channel`. You can then do natural things like:
|
||||
defines a type `Channel`. You can then do natural things such as:
|
||||
|
||||
```cpp
|
||||
Channel channel = Channel::Green;
|
||||
|
||||
channel.to_string(); // Results in the string "Green"
|
||||
channel.to_integral(); // Results in the int 2
|
||||
channel._to_string(); // Results in the string "Green"
|
||||
channel._to_integral(); // Results in the int 2
|
||||
|
||||
Channel::_from_string("Red"); // Results in Channel::Red
|
||||
Channel::_from_integral(3); // Results in Channel::Blue
|
||||
|
||||
constexpr auto channel = Channel::_from_integral(3);
|
||||
// Do it at compile time
|
||||
constexpr auto channel = Channel::_max;
|
||||
// Channel::Blue
|
||||
// Do it at compile time (C++11 only)
|
||||
|
||||
for (Channel channel : Channel::_values) {
|
||||
for (Channel channel : Channel::_values()) {
|
||||
// Iterate over all channels
|
||||
}
|
||||
```
|
||||
|
||||
...and more. See the
|
||||
[project page](http://aantron.github.io/better-enums#tutorial) and
|
||||
[examples](https://github.com/aantron/better-enums/tree/master/example) for a
|
||||
tutorial.
|
||||
...and more. See the [project page](http://aantron.github.io/better-enums).
|
||||
|
||||
## Installation
|
||||
|
||||
Simply add `enum.h` from `master` to your project.
|
||||
Simply add `enum.h` to your project.
|
||||
|
||||
## Features
|
||||
|
||||
- Generated at compile time by `constexpr` functions and the preprocessor.
|
||||
- Safe conversions between enums and integers and strings.
|
||||
[1-basic.cc](https://github.com/aantron/better-enums/blob/master/example/1-basic.cc)
|
||||
- Iterable collections of constants and names.
|
||||
[2-iterate.cc](https://github.com/aantron/better-enums/blob/master/example/2-iterate.cc)
|
||||
- Range information, such as the number of constants defined and the maximum
|
||||
constant.
|
||||
[2-iterate.cc](https://github.com/aantron/better-enums/blob/master/example/2-iterate.cc)
|
||||
- Requires no external utility.
|
||||
- Safe conversions to/from integers and strings.
|
||||
- Iteration over declared values.
|
||||
- Switch case checking.
|
||||
[3-switch.cc](https://github.com/aantron/better-enums/blob/master/example/3-switch.cc)
|
||||
- All operations are `constexpr` and can be used at compile time in your own
|
||||
`constexpr` code.
|
||||
[4-constexpr.cc](https://github.com/aantron/better-enums/blob/master/example/4-constexpr.cc)
|
||||
- Constant values can be set (`Red = 1`) and aliased (`Favorite = Green`), just
|
||||
like with built-in enums.
|
||||
`constexpr` code. See demos on the
|
||||
[project page](http://aantron.github.io/better-enums) for how to define
|
||||
default values, for example.
|
||||
- Constant values can be initialized with expressions (`Red = 1`) and aliased
|
||||
(`Favorite = Green`), just like with built-in enums.
|
||||
- Generating a large number of enums is about as fast as including a typical
|
||||
standard header like `iostream` – performance test included.
|
||||
- Explicit choice of underlying representation type.
|
||||
- Header-only.
|
||||
- No dependencies besides the standard library.
|
||||
- Tested on gcc 4.9 and clang 3.6.
|
||||
- Tested on gcc 4.3+ and clang 3.3+.
|
||||
|
||||
The library is standard C++, but it compiles only with gcc and clang due to
|
||||
lagging C++11 support in msvc. A future runtime fallback version will allow
|
||||
msvc and non-C++11 usage.
|
||||
Visual C++ support coming in the next few days. I am currently porting.
|
||||
|
||||
## Contact
|
||||
|
||||
Don't hesitate to contact me about features (or bugs!):
|
||||
<a href="mailto:antonbachin@yahoo.com">antonbachin@yahoo.com</a>
|
||||
|
||||
## Explanation
|
||||
|
||||
The `ENUM` macro specializes a template based around a regular `enum`
|
||||
declaration, though it is more similar to `enum class` in the degree of type
|
||||
safety. The following are spiritually equivalent:
|
||||
|
||||
ENUM(Channel, int, Red = 1, Green, Blue);
|
||||
enum class Channel : int {Red = 1, Green, Blue};
|
||||
|
||||
ENUM(Depth, char, Indexed8Bit, HighColor, TrueColor);
|
||||
enum class Depth : char {Indexed8Bit, HighColor, TrueColor};
|
||||
|
||||
See the full [documentation](http://aantron.github.io/better-enums).
|
||||
|
||||
## Development plan
|
||||
|
||||
There are several areas that still need improvement.
|
||||
|
||||
- Some enum types might have a sensible choice for a default constructor. The
|
||||
library should allow it to be customized.
|
||||
- All safety checks are currently done by linear scans. This may be a
|
||||
performance problem for enum types with many constants.
|
||||
- Better diagnostics for empty enums or too many constants.
|
||||
- Conversions from integers and strings that don't throw exceptions, but
|
||||
indicate failure by some other means.
|
||||
|
||||
## License
|
||||
|
||||
Better Enums is released under the BSD 2-clause license. See
|
||||
|
||||
272
doc/ApiReference.md
Normal file
272
doc/ApiReference.md
Normal file
@ -0,0 +1,272 @@
|
||||
## API reference
|
||||
|
||||
Table of contents
|
||||
|
||||
### Overview
|
||||
|
||||
The following declaration
|
||||
|
||||
#include <enum.h>
|
||||
ENUM(Enum, underlying_type, A, B, C, ...);
|
||||
|
||||
generates a new type `Enum`. It is notionally similar to the type created by
|
||||
this $cxx11 declaration:
|
||||
|
||||
enum class Enum : underlying_type {A, B, C, ...};
|
||||
|
||||
that is, `Enum` is a scoped enumerated type with constants `Enum::A`, `Enum::B`,
|
||||
`Enum::C`, and so on, with memory representation the same as `underlying_type`.
|
||||
It is possible to supply initializers for any of the constants:
|
||||
|
||||
ENUM(Enum, underlying_type, A = 1, B = constant_expression, C, ...);
|
||||
|
||||
The initializers have the same meaning and constraints as in an `enum class`
|
||||
declaration.
|
||||
|
||||
The principal differences between the types declared by the `ENUM` macro and
|
||||
`enum class` are:
|
||||
|
||||
- `ENUM` is available for $cxx98 for compilers supporting `__VA_ARGS__`,
|
||||
- the `ENUM` type is implicitly convertible to integral types, though this can
|
||||
be disabled when using $cxx11, [How]() and
|
||||
- the `ENUM` type supports a set of reflective operations, detailed in the
|
||||
rest of this reference.
|
||||
|
||||
The types produced by the `ENUM` macro are sometimes called Better Enums in the
|
||||
rest of this reference, and the running example declaration is
|
||||
|
||||
ENUM(Enum, int, A, B, C);
|
||||
|
||||
For the purposes of argument passing, Better Enums should be thought of as
|
||||
equivalent to their underlying type. This means that Better Enums are typically
|
||||
integers that fit into a register or a stack word, and should be passed by
|
||||
value.
|
||||
|
||||
All names declared in the scope of a Better Enum are prefixed with an underscore
|
||||
in order to avoid conflicts with potential constant names.
|
||||
|
||||
|
||||
|
||||
### General
|
||||
|
||||
#### typedef _enumerated
|
||||
|
||||
An internal type used to declare constants. `ENUM` generates something similar
|
||||
to
|
||||
|
||||
struct Enum {
|
||||
enum _enumerated : int { A, B, C };
|
||||
// ...
|
||||
};
|
||||
|
||||
The user needs to be aware of this type in only one situation. A literal constant
|
||||
such as `Enum::A` is an expression of type `Enum::_enumerated`, not `Enum`. It
|
||||
is not possible to directly call a method on the value, as in
|
||||
`Enum::A._to_string()`. This problem is addressed by operator `+` below.
|
||||
|
||||
#### non-member constexpr Enum unary operator +(_enumerated)
|
||||
|
||||
Forces promotion of `Enum::_enumerated` to `Enum`. Provided to solve the problem
|
||||
described under `_enumerated` above. So, for example, it is necessary to write
|
||||
`(+Enum::A)._to_string()` instead of `Enum::A._to_string`.
|
||||
|
||||
#### constexpr implicit constructor Enum(_enumerated)
|
||||
|
||||
A constructor that performs implicit conversions of `Enum::_enumerated` to
|
||||
`Enum`. This allows code to use a literal constant where `Enum` is expected in
|
||||
most situations — those where the compiler can do an implicit conversion.
|
||||
For example, if there is a function `void output(Enum)`, it can be called as
|
||||
`output(Enum::A)`, without having to write `output(+Enum::A)`. This constructor
|
||||
is also used for the typical initialization
|
||||
|
||||
Enum value = Enum::A;
|
||||
|
||||
The other constructors of `Enum` are the implicitly-generated copy and move
|
||||
constructors. There is no default constructor.
|
||||
|
||||
#### static constexpr size_t _size
|
||||
|
||||
The number of constants declared. `Enum::_size == 3`.
|
||||
|
||||
#### typedef _value_iterable
|
||||
|
||||
Type of object that permits iteration over the constants. Has at least
|
||||
`constexpr` `begin()`, `end()`, and `size()` methods, and `operator[]`.
|
||||
Iteration visits each declared constant, even if multiple constants have the
|
||||
same value, and visits them in order of declaration. See usage examples under
|
||||
`_values`.
|
||||
|
||||
#### typedef _value_iterator
|
||||
|
||||
Random-access iterator type for `_value_iterable`. Most operations, including
|
||||
dereferencing, are `constexpr`. The exceptions are mutating operators such as
|
||||
`operator++`. In `constexpr` code, that can be replaced with addition of `1`.
|
||||
|
||||
#### static constexpr _value_iterable _values()
|
||||
|
||||
`constexpr` access to the collection of declared constants. For example:
|
||||
|
||||
for (size_t index = 0; index < Enum::_values().size(); ++index)
|
||||
output(Enum::_values()[index]);
|
||||
|
||||
or, using iterators:
|
||||
|
||||
for (Enum::_value_iterator iterator = Enum::_values().begin();
|
||||
iterator != Enum::_values().end(); ++iterator) {
|
||||
|
||||
output(*iterator);
|
||||
}
|
||||
|
||||
or, in $cxx11:
|
||||
|
||||
for (Enum value : Enum::_values())
|
||||
output(value);
|
||||
|
||||
#### non-member struct better_enums::optional<Enum>
|
||||
|
||||
Optional `Enum` value. An optional value `maybe` can be in one of two states:
|
||||
|
||||
- `(bool)maybe == true`, meaning that there is `Enum` value, and `*maybe`
|
||||
evaluates to it, and
|
||||
- `(bool)maybe == false`, meaning no `Enum` value is available.
|
||||
|
||||
This type is referred to as simply `optional` in the rest of the reference, as
|
||||
if `using better_enums::optional;` has been entered.
|
||||
|
||||
|
||||
|
||||
### Strings
|
||||
|
||||
#### member constexpr? const char* _to_string() const
|
||||
|
||||
Returns the string representation of the Better Enum value on which it is
|
||||
called. For example, if `value == Enum::A`, `value._to_string()` is the same
|
||||
string as `"A"`.
|
||||
|
||||
If two constants have the same numeric representation, and `value` is equal to
|
||||
one of those constants, then it has that numeric representation, and it is
|
||||
undefined which constant's name `_to_string` will choose.
|
||||
|
||||
If `value` is not equal to the representation of any declared constant, for
|
||||
example if it was obtained using an unchecked cast such as:
|
||||
|
||||
Enum value = Enum::_from_integral_unchecked(0xbadc0de);
|
||||
|
||||
then the behavior of `value._to_string` is undefined.
|
||||
|
||||
Runnig time is linear in the number of declared constants.
|
||||
|
||||
This method is not `constexpr` by default because making it so entails a large
|
||||
slowdown during compilation. [See here]() for how to make it `constexpr` for
|
||||
some or all Better Enums.
|
||||
|
||||
#### static constexpr Enum _from_string(const char*)
|
||||
|
||||
If the given string is the exact name of a declared constant, returns its value.
|
||||
Otherwise, throws `std::runtime_error`. Running time is linear in the number of
|
||||
declared constants multiplied by the length of the longest constant.
|
||||
|
||||
#### static constexpr optional<Enum> _from_string_nothrow(const char*)
|
||||
|
||||
The same as `_from_string`, but does not throw an exception on failure. Returns
|
||||
an [optional]() value instead.
|
||||
|
||||
#### static constexpr Enum _from_string_nocase(const char*)
|
||||
|
||||
The same as `_from_string`, but comparison is up to case, in the usual sense in
|
||||
the Latin-1 encoding.
|
||||
|
||||
#### static constexpr optional<Enum> _from_string_nocase_nothrow(const char*)
|
||||
|
||||
Is to `_from_string_nocase` as `_from_string_nothrow` is to `_from_string`.
|
||||
|
||||
#### static constexpr bool _is_valid(const char*)
|
||||
|
||||
Evaluates to `true` if and only if the given string is the exact name of a
|
||||
declared constant. Running time is the same as for `_from_string`.
|
||||
|
||||
#### static constexpr bool _is_valid_nocase(const char*)
|
||||
|
||||
The same as `_is_valid`, but comparison is done up to case as in
|
||||
`_from_string_nocase`.
|
||||
|
||||
#### static constexpr const char* _name()
|
||||
|
||||
Evaluates to the name of the Better Enum type. `Enum::_name()` is the same
|
||||
string as `"Enum"`.
|
||||
|
||||
#### typedef _name_iterable
|
||||
|
||||
Type of object that permits iteration over names of declared constants. Has at
|
||||
least `constexpr` `begin()`, `end()`, and `size()` methods. `operator[]` is also
|
||||
available, but is `constexpr` if and only if `_to_string` is `constexpr`.
|
||||
Iteration visits constants in order of declaration. See usage example under
|
||||
`_names`.
|
||||
|
||||
#### typedef _name_iterator
|
||||
|
||||
Random-access iterator type for `_name_iterable`. Most operations are
|
||||
`constexpr`, but dereferencing is `constexpr` if and only if `_to_string` is
|
||||
`constexpr`. Mutating operators such as `operator++` are not `constexpr` due to
|
||||
their nature — adding `1` is a `constexpr` alternative.
|
||||
|
||||
#### static constexpr? _name_iterable _names()
|
||||
|
||||
Access to the collection of declared constant names. For example:
|
||||
|
||||
for (size_t index = 0; index < Enum::_names().size(); ++index)
|
||||
std::cout << Enum::_names()[index] << std::endl;
|
||||
|
||||
or, using iterators:
|
||||
|
||||
for (Enum::_name_iterator iterator = Enum::_names().begin();
|
||||
iterator != Enum::_names().end(); ++iterator) {
|
||||
|
||||
std::cout << *iterator << std::endl;
|
||||
}
|
||||
|
||||
or, in $cxx11:
|
||||
|
||||
for (const char *name : Enum::_names())
|
||||
std::cout << name << std::endl;
|
||||
|
||||
`constexpr` if and only if `_to_string` is `constexpr`.
|
||||
|
||||
|
||||
|
||||
### Integers
|
||||
|
||||
#### typedef _integral
|
||||
|
||||
The *underlying* or *representation* type of the Better Enum. For example,
|
||||
`Enum::_integral` is the same type as `int`. Each Better Enum has the same size
|
||||
and alignment requirement as its representation type. This is true even under
|
||||
$cxx98.
|
||||
|
||||
#### member constexpr _integral _to_integral() const
|
||||
|
||||
No-op conversion of a Better Enum to a value of its representation type. For
|
||||
example, `(+Enum::C)._to_integral() == 2`.
|
||||
|
||||
#### static constexpr Enum _from_integral(_integral)
|
||||
|
||||
Checked conversion of an integer to a Better Enum value. The check runs in time
|
||||
linear in the number of declared constants, but the conversion itself is a
|
||||
no-op. Throws `std::runtime_error` if the given integer is not the numeric value
|
||||
of one of the declared constants.
|
||||
|
||||
#### static constexpr Enum _from_integral_unchecked(_integral)
|
||||
|
||||
No-op unchecked conversion of an integer to a Better Enum value. If the given
|
||||
integer is not the numeric value of one of the declared constants, the behavior
|
||||
of all subsequent operations on the Better Enum value is undefined.
|
||||
|
||||
#### static constexpr optional<Enum> _from_integral_nothrow(_integral)
|
||||
|
||||
Checked conversion as `_from_integral`, but does not throw an exception on
|
||||
failure. Returns an [optional]() value instead.
|
||||
|
||||
#### static constexpr bool _is_valid(_integral)
|
||||
|
||||
Evaluates to `true if and only if the given integer is the numeric value of one
|
||||
of the declared constants.
|
||||
67
doc/CompilerSupport.md
Normal file
67
doc/CompilerSupport.md
Normal file
@ -0,0 +1,67 @@
|
||||
## Compiler support
|
||||
|
||||
Better Enums aims to support as many compilers as is reasonable. It has been
|
||||
tested with clang++ and g++, and Visual C++ support is coming in the next few
|
||||
days.
|
||||
|
||||
In principle, Better Enums can be used with any compiler that supports either
|
||||
|
||||
- $cxx11
|
||||
- $cxx98 with the variadic macro (`__VA_ARGS__`) extension
|
||||
|
||||
This should make it compatible with Visual C++ 2005 and higher/
|
||||
|
||||
To ensure that nothing is broken, every release of Better Enums is tested
|
||||
automatically with a large number of combinations of compiler and configuration.
|
||||
The full list is given at the end of this page.
|
||||
|
||||
The tests include compiling and running unit tests, all the examples in the
|
||||
demos and tutorials, and a multiple translation unit linking test.
|
||||
|
||||
### Tested configurations
|
||||
|
||||
~~~comment
|
||||
clang++36 -std=c++11
|
||||
clang++36 -std=c++11 -DBETTER_ENUMS_STRICT_CONVERSION
|
||||
clang++36 -std=c++11 -DBETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
clang++36 -std=c++98
|
||||
clang++35 -std=c++11
|
||||
clang++35 -std=c++11 -DBETTER_ENUMS_STRICT_CONVERSION
|
||||
clang++35 -std=c++11 -DBETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
clang++35 -std=c++98
|
||||
clang++34 -std=c++11
|
||||
clang++34 -std=c++11 -DBETTER_ENUMS_STRICT_CONVERSION
|
||||
clang++34 -std=c++11 -DBETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
clang++34 -std=c++98
|
||||
clang++33 -std=c++11
|
||||
clang++33 -std=c++11 -DBETTER_ENUMS_STRICT_CONVERSION
|
||||
clang++33 -std=c++11 -DBETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
clang++33 -std=c++98
|
||||
g++51 -std=c++11
|
||||
g++51 -std=c++11 -DBETTER_ENUMS_STRICT_CONVERSION
|
||||
g++51 -std=c++11 -DBETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
g++51 -std=c++98
|
||||
g++49 -std=c++11
|
||||
g++49 -std=c++11 -DBETTER_ENUMS_STRICT_CONVERSION
|
||||
g++49 -std=c++11 -DBETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
g++49 -std=c++98
|
||||
g++48 -std=c++11
|
||||
g++48 -std=c++11 -DBETTER_ENUMS_STRICT_CONVERSION
|
||||
g++48 -std=c++11 -DBETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
g++48 -std=c++98
|
||||
g++47 -std=c++11
|
||||
g++47 -std=c++11 -DBETTER_ENUMS_STRICT_CONVERSION
|
||||
g++47 -std=c++11 -DBETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
g++47 -std=c++98
|
||||
g++46 -std=c++0x -DBETTER_ENUMS_NO_CONSTEXPR
|
||||
g++46 -std=c++0x -DBETTER_ENUMS_NO_CONSTEXPR -DBETTER_ENUMS_STRICT_CONVERSION
|
||||
g++46 -std=c++98
|
||||
g++45 -std=c++0x -DBETTER_ENUMS_NO_CONSTEXPR
|
||||
g++45 -std=c++0x -DBETTER_ENUMS_NO_CONSTEXPR -DBETTER_ENUMS_STRICT_CONVERSION
|
||||
g++45 -std=c++98
|
||||
g++44 -std=c++0x -DBETTER_ENUMS_NO_CONSTEXPR
|
||||
g++44 -std=c++0x -DBETTER_ENUMS_NO_CONSTEXPR -DBETTER_ENUMS_STRICT_CONVERSION
|
||||
g++44 -std=c++98
|
||||
g++43 -std=c++0x -DBETTER_ENUMS_NO_CONSTEXPR
|
||||
g++43 -std=c++98
|
||||
~~~
|
||||
41
doc/ExtendingMacroLimits.md
Normal file
41
doc/ExtendingMacroLimits.md
Normal file
@ -0,0 +1,41 @@
|
||||
## Extending macro limits
|
||||
|
||||
The `ENUM` macro makes heavy use of the preprocessor. The preprocessor can't
|
||||
perform intelligent operations on sequences of tokens with arbitrary lengths
|
||||
— the operations must be bounded. The result is that there are two size
|
||||
limits: on the number of constants that a Better Enum can have, and on the
|
||||
maximum length of a constant name under certain conditions. If you run into
|
||||
either of these limits, follow the steps on this page to increase them.
|
||||
|
||||
The conditions for the constant name length limit rarely apply. They are:
|
||||
|
||||
- you are compiling an enum with compile-time string trimming, a.k.a. a
|
||||
"slow enum", which is only enabled when you do [this](), and
|
||||
- you have a constant with an initializer.
|
||||
|
||||
Constants without initializers can always have arbitrarily-long names.
|
||||
|
||||
The default limits are 64 constants and 23 characters for names of slow enum
|
||||
constants that have initializers. To extend these limits:
|
||||
|
||||
1. Pick your desired limits. I will use 512 constants and 63 characters as an
|
||||
example. Add 1 to the number of characters to account for the null
|
||||
terminator — our numbers are now 512 and 64.
|
||||
2. Get `make_macros.py` from your copy of the Better Enums distribution or
|
||||
from [GitHub]().
|
||||
3. You will run this script to generate a header file containing some
|
||||
replacement macros for `enum.h` to use. Pick a name for this file and a
|
||||
location somewhere in your include path. I will assume that this file is
|
||||
`common/enum_macros.h`.
|
||||
4. Run `python make_macros.py 512 64 > common/enum_macros.h`.
|
||||
5. Define `BETTER_ENUMS_MACRO_FILE <common/enum_macros.h>` before including
|
||||
`enum.h`. This is typically done by supplying extra flags to the compiler
|
||||
on the command line:
|
||||
|
||||
- For g++ and clang++, `-DBETTER_ENUMS_MACRO_FILE='<common/enum_macros.h>'`
|
||||
- For VC++, `\DBETTER_ENUMS_MACRO_FILE='<common/enum_macros.h>'`
|
||||
|
||||
6. Enjoy the looser limits. Just watch out — increasing the second
|
||||
number can really slow down compilation of compile-time trimming enums.
|
||||
7. You don't need `make_macros.py` anymore. It's not part of your build
|
||||
process and you can delete it.
|
||||
24
doc/Makefile
Normal file
24
doc/Makefile
Normal file
@ -0,0 +1,24 @@
|
||||
SOURCE_MARKDOWN := $(wildcard tutorial/*) $(wildcard demo/*)
|
||||
SOURCE_CXX := $(SOURCE_MARKDOWN:.md=.cc)
|
||||
|
||||
.PHONY : all
|
||||
all : html examples
|
||||
|
||||
.PHONY : html
|
||||
html :
|
||||
python docs.py
|
||||
|
||||
.PHONY : examples
|
||||
examples : clean-examples $(SOURCE_CXX)
|
||||
|
||||
.PHONY : clean-examples
|
||||
clean-examples :
|
||||
make -C ../example clean
|
||||
rm -f ../example/*.cc
|
||||
|
||||
%.cc : %.md
|
||||
python transform.py --o-cxx ../example/$(notdir $@) $<
|
||||
|
||||
.PHONY : clean
|
||||
clean :
|
||||
rm -rf html
|
||||
109
doc/OptInFeatures.md
Normal file
109
doc/OptInFeatures.md
Normal file
@ -0,0 +1,109 @@
|
||||
## Opt-in features
|
||||
|
||||
Better Enums has a few opt-in features that affect how enums are generated. The
|
||||
default configuration is suitable for general use, but you might have more
|
||||
stringent requirements. This page describes the optional features and how to
|
||||
enable them.
|
||||
|
||||
### Strict conversions
|
||||
|
||||
Each Better Enum is implicitly convertible to its member `enum _enumerated`
|
||||
type. This is meant to support usage of Better Enums directly in `switch`
|
||||
statements. When you write
|
||||
|
||||
switch (channel) {
|
||||
...
|
||||
}
|
||||
|
||||
the compiler applies the implicit conversion, and is also able to do case
|
||||
exhaustiveness checking. Unfortunately, `_enumerated` is always a regular $cxx98
|
||||
enum type, meaning that it has standard conversions to integral types. The end
|
||||
result is `channel` is implicitly convertible all the way to `int` —
|
||||
behavior that is often considered a violation of type safety.
|
||||
|
||||
In $cxx11, you can force Better Enums to declare an internal `enum class` type
|
||||
to use for `switch` statements. Each enum will then be only convertible to its
|
||||
`enum class`, and won't be implicitly convertible to integers. This is done by
|
||||
defining `BETTER_ENUMS_STRICT_CONVERSION` before including `enum.h`. You would
|
||||
typically do this on the compiler's command line.
|
||||
|
||||
The reason strict conversions aren't enabled by default in $cxx11 is that doing
|
||||
so would break compatibility with the $cxx98 interface.
|
||||
|
||||
- The "weaker" incompatibility is that you could write a bunch of $cxx98 code
|
||||
that relies on implicit integer conversions, and then try to switch to
|
||||
$cxx11. The code would then fail to compile. An example where implicit
|
||||
conversions are an "advantage" is when using Better Enums as arguments to
|
||||
the methods of `std::bitset`. I could have ignored this problem by declaring
|
||||
usage of implicit integer conversions unsupported, but in light of the next
|
||||
issue, I decided not to do that.
|
||||
- The "stronger" incompatibility is a difference in how `switch` cases must be
|
||||
written. The syntaxes for the two variants, implicitly-converting and
|
||||
strict, are mutually exclusive.
|
||||
|
||||
Here they are:
|
||||
|
||||
// Default variant
|
||||
switch (channel) {
|
||||
case Channel::Red: break;
|
||||
case Channel::Green: break;
|
||||
case Channel::Blue: break;
|
||||
}
|
||||
|
||||
// Strict variant
|
||||
switch (channel) {
|
||||
case +Channel::Red: break;
|
||||
case +Channel::Green: break;
|
||||
case +Channel::Blue: break;
|
||||
}
|
||||
|
||||
I would be very happy to make conversion to `enum class` the default whenever
|
||||
`enum class` is available, but how to do so without breaking compatibility is so
|
||||
far an open problem.
|
||||
|
||||
### Compile-time name trimming
|
||||
|
||||
Better Enums is able to do all of its work at compile time. There is one task,
|
||||
however, at which my current method is pretty slow on modern compilers. The
|
||||
performance is still reasonable, but it makes enums take about four times longer
|
||||
to compile, compared to deferring the task to program startup. The task is
|
||||
trimming stringized constant names.
|
||||
|
||||
The problem arises when the preprocessor stringizes an initializer. For example,
|
||||
|
||||
ENUM(Channel, int, Red = 1, Green, Blue);
|
||||
|
||||
results in an internal array, somewhere inside the generated Better Enum, that
|
||||
looks like
|
||||
|
||||
names = {"Red = 1", "Green", "Blue"}
|
||||
|
||||
Before the name of `Channel::Red` can be returned to the user, the initializer
|
||||
` = 1` must be trimmed off. This is the part that is slow to compile, and is
|
||||
deferred to startup by default.
|
||||
|
||||
If you want to enable it at compile time, you have two options. The first is to
|
||||
use an alternative `SLOW_ENUM` macro to declare your enum. It will enable
|
||||
compile-time trimming for that enum only. If you only do this for a few enums,
|
||||
you probably won't notice the difference in compilation time.
|
||||
|
||||
You can also enable compile-time trimming globally for all enums by defining
|
||||
`BETTER_ENUMS_CONSTEXPR_TO_STRING` before including `enum.h`. Typically, you
|
||||
would do this by supplying a command-line argument to your compiler.
|
||||
|
||||
The result of doing either one is that `_to_string` and `_names` will become
|
||||
`constexpr` for your compile-time enjoyment.
|
||||
|
||||
The reason this option is not enabled by default when avaialble, besides the
|
||||
fact that the current implementation is slow, is that I don't believe most users
|
||||
need it most of the time.
|
||||
|
||||
As a note, the performance is not *that* bad. You still have to define on the
|
||||
order of 10+ slow enums in order to slow compilation down as much as merely
|
||||
including `iostream` does. However, it is shockingly slower than the faster
|
||||
implementation, where you have the leeway to define 40+ enums before you reach
|
||||
the same level of slowdown as `iostream` gives you.
|
||||
|
||||
There are enough other problems with slow enums, however, like potential symbol
|
||||
pollution in the final binaries, that I decided to leave them as an opt-in
|
||||
feature until they improve to the point where they can be the default.
|
||||
938
doc/api.html
938
doc/api.html
@ -1,938 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<link rel="canonical" href="http://aantron.github.io/better-enums/api.html" />
|
||||
|
||||
<title>Better Enums - Reference</title>
|
||||
|
||||
<meta name="description" content="Clean, reflective enums for C++11. Conversions
|
||||
from/to string, constant list, and compile-time operations. Natural syntax
|
||||
and a high degree of type safety. Generator-free in typical usage. The
|
||||
library is header-only and easy to install. Since most operations are
|
||||
constexpr, they can be used in your own compile-time code. The library
|
||||
depends only on the standard library and one type of compiler extension. It
|
||||
is therefore almost entirely standard C++. This page gives reference
|
||||
documentation" />
|
||||
|
||||
<meta name="author" content="Anton Bachin" />
|
||||
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
|
||||
<style>
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
|
||||
width: 100%;
|
||||
padding: 0 3em;
|
||||
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
h1 span {
|
||||
font-size: 50%;
|
||||
margin-left: 1em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 150%;;
|
||||
}
|
||||
|
||||
a:not(#index):target + h2 {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
h2 ~ p, h2 ~ pre {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p.started {
|
||||
margin-bottom: 4em;
|
||||
}
|
||||
|
||||
p + p {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
pre, tt, h3 {
|
||||
background-color: #f4f4f4;
|
||||
border-bottom: 1px solid #ccc;
|
||||
font-family: "Lucida Console", monospace;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 1em 20px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
tt {
|
||||
margin: 0 1px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
display: inline-block;
|
||||
margin: 2em 0 1em 0;
|
||||
padding: 4px 10px;
|
||||
border: 0;
|
||||
background-color: #e0e0e0;
|
||||
white-space: nowrap;
|
||||
overflow: scroll;
|
||||
text-shadow: 0 1px white;
|
||||
}
|
||||
|
||||
a:target + h3 {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
h2 + h3 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
h3 + br + h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h3 span {
|
||||
opacity: 0.6;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
main {
|
||||
margin: 5em 3em 5em 3em;
|
||||
}
|
||||
|
||||
main > div {
|
||||
-webkit-columns: 32.5em;
|
||||
-moz-columns: 32.5em;
|
||||
columns: 32.5em;
|
||||
|
||||
-webkit-column-gap: 6em;
|
||||
-moz-column-gap: 6em;
|
||||
column-gap: 6em;
|
||||
}
|
||||
|
||||
@media (max-width: 1320px) {
|
||||
main {
|
||||
margin-left: 2em;
|
||||
margin-right: 2em;
|
||||
}
|
||||
|
||||
main > div {
|
||||
-webkit-column-gap: 4em;
|
||||
-moz-column-gap: 4em;
|
||||
column-gap: 4em;
|
||||
}
|
||||
|
||||
header {
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 650px) {
|
||||
nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
header {
|
||||
padding-left: 0.5em;
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
h2 ~ p, h2 ~ pre {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
h3 span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
main > section:nth-child(2) > h2 {
|
||||
-webkit-column-span: all;
|
||||
-moz-column-span: all;
|
||||
column-span: all;
|
||||
}
|
||||
|
||||
main > div > section:not(:first-child):not(:nth-child(2)), ul {
|
||||
-webkit-column-break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
main section {
|
||||
padding-top: 5em;
|
||||
max-width: 50em;
|
||||
}
|
||||
|
||||
main > section:nth-child(2) {
|
||||
-webkit-columns: 14em;
|
||||
}
|
||||
|
||||
h4, ul {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
padding-top: 2em;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
section > h2 > a:last-child {
|
||||
float: right;
|
||||
font-size: 60%;
|
||||
position: relative;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
font-weight: normal;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
a[id] {
|
||||
display: block;
|
||||
position: relative;
|
||||
top: -7em;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: blue;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
p a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tt a {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
h2 > a:first-child {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
header a {
|
||||
color: white;
|
||||
margin-right: 3em;
|
||||
}
|
||||
|
||||
h1 a:hover {
|
||||
text-decoration: none;
|
||||
text-shadow: 0 0 2px white;
|
||||
}
|
||||
|
||||
nav {
|
||||
float: right;
|
||||
position: relative;
|
||||
top: 2.3em;
|
||||
right: 6em;
|
||||
}
|
||||
|
||||
em {
|
||||
padding: 0 2px;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin: 0.5em;
|
||||
opacity: 0.6;
|
||||
font-size: 70%;
|
||||
}
|
||||
|
||||
span.cpp {
|
||||
letter-spacing: -1px;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
span.eleven {
|
||||
letter-spacing: -2px;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<a href="https://github.com/aantron/better-enums">
|
||||
GitHub
|
||||
</a>
|
||||
<a href="http://aantron.github.io/better-enums#tutorial">
|
||||
Tutorial
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<h1>
|
||||
<a href="https://github.com/aantron/better-enums">
|
||||
Better Enums <span>0.8.0</span>
|
||||
</a>
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<section>
|
||||
|
||||
<p class="started">
|
||||
The best way to get started with Better Enums is to read the
|
||||
<a href="http://aantron.github.io/better-enums">project page</a> and browse
|
||||
the commented
|
||||
<a href="https://github.com/aantron/better-enums/tree/master/example">
|
||||
examples</a>. This page gives reference documentation.
|
||||
</p>
|
||||
|
||||
<p>The following declaration</p>
|
||||
|
||||
<pre>#include <enum.h>
|
||||
ENUM(Enum, underlying_type, A, B, C);</pre>
|
||||
|
||||
<p>
|
||||
generates a new type <tt>Enum</tt>. It is notionally similar to the type
|
||||
created by this declaration:
|
||||
</p>
|
||||
|
||||
<pre>enum class Enum : underlying_type {A, B, C};</pre>
|
||||
|
||||
<p>
|
||||
that is, it is an enumerated type with constants <tt>Enum::A</tt>,
|
||||
<tt>Enum::B</tt>, and <tt>Enum::C</tt>, and is represented in memory by an
|
||||
integer of type <tt>underlying_type</tt>. Just like with a built-in
|
||||
<tt>enum class</tt>, it is possible to specify numeric values and aliases
|
||||
for constants:
|
||||
</p>
|
||||
|
||||
<pre>ENUM(Enum, underlying_type, A = 1, B, C, D = A);</pre>
|
||||
|
||||
<p>
|
||||
Constant values are assigned by the compiler by exactly the same rules as
|
||||
for a built-in enum, so in the above example, <tt>Enum::A == 1</tt>,
|
||||
<tt>Enum::B == 2</tt>, <tt>Enum::C == 3</tt>, and <tt>Enum::D == 1</tt>.
|
||||
</p>
|
||||
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
||||
<a id="index"></a>
|
||||
<h2>Member index</h2>
|
||||
|
||||
<ul>
|
||||
<h4><a href="#enumerated">Internal enumerated type</a></h4>
|
||||
<li><a href="#typename-enumerated">typename _Enumerated</a></li>
|
||||
<li><a href="#constructor">Enum(_Enumerated)</a></li>
|
||||
<li><a href="#operator+">operator +(_Enumerated)</a></li>
|
||||
<li><a href="#cast">operator _Enumerated()</a></li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<h4><a href="#integral">Integral type</a></h4>
|
||||
<li><a href="#typename-integral">typename _Integral</a></li>
|
||||
<li><a href="#from_integral">_from_integral</a></li>
|
||||
<li><a href="#from_integral_unchecked">_from_integral_unchecked</a></li>
|
||||
<li><a href="#to_integral">to_integral</a></li>
|
||||
<li><a href="#is_valid-integral">_is_valid</a></li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<h4><a href="#string">String conversions</a></h4>
|
||||
<li><a href="#from_string">_from_string</a></li>
|
||||
<li><a href="#from_string_nocase">_from_string_nocase</a></li>
|
||||
<li><a href="#to_string">to_string</a></li>
|
||||
<li><a href="#name">_name</a></li>
|
||||
<li><a href="#is_valid-string">_is_valid</a></li>
|
||||
<li><a href="#is_valid_nocase">_is_valid_nocase</a></li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<h4><a href="#iteration">Iteration</a></h4>
|
||||
<li><a href="#values">_values</a></li>
|
||||
<li><a href="#names">_names</a></li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<h4><a href="#range">Range properties</a></h4>
|
||||
<li><a href="#first">_first</a></li>
|
||||
<li><a href="#last">_last</a></li>
|
||||
<li><a href="#min">_min</a></li>
|
||||
<li><a href="#max">_max</a></li>
|
||||
<li><a href="#size">_size</a></li>
|
||||
<li><a href="#span">_span</a></li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<h4><a href="#comparison">Comparisons</a></h4>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<h4><a href="#safety">Additional type safety</a></h4>
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
|
||||
<section>
|
||||
|
||||
<a id="enumerated"></a>
|
||||
<h2>
|
||||
<a href="#enumerated">Internal enumerated type</a>
|
||||
<a href="#index">index</a>
|
||||
</h2>
|
||||
|
||||
<a id="typename-enumerated"></a>
|
||||
<h3><span>typename</span> Enum::_Enumerated</h3>
|
||||
|
||||
<p>
|
||||
The declared type <tt>Enum</tt> is built around an internal
|
||||
<span class="cpp">C++</span> enumeration. Notionally,
|
||||
</p>
|
||||
|
||||
<pre>ENUM(Enum, int, A, B, C);</pre>
|
||||
|
||||
<p>produces</p>
|
||||
|
||||
<pre>class Enum {
|
||||
...
|
||||
public:
|
||||
enum _Enumerated : int {A, B, C};
|
||||
...
|
||||
};</pre>
|
||||
|
||||
<p>
|
||||
<tt>_Enumerated</tt> is simply the name of the internal enumeration. The
|
||||
user should not use this type name directly, but it is referred to in the
|
||||
rest of the documentation. The name is exposed because a literal
|
||||
<tt>Enum::A</tt> is not a value of type <tt>Enum</tt>, but a value of type
|
||||
<tt>Enum::_Enumerated</tt>, which is convertible to <tt>Enum</tt>, in most
|
||||
cases implicitly.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Note that <tt>_Enumerated</tt> is not a
|
||||
<span class="cpp">C++</span><span class="eleven">11</span>
|
||||
<tt>enum class</tt>.
|
||||
</p>
|
||||
|
||||
<a id="constructor"></a>
|
||||
<h3>
|
||||
<span>implicit constructor constexpr</span> Enum(_Enumerated)
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
A converting constructor for promoting values of type <tt>_Enumerated</tt>
|
||||
to values of type <tt>Enum</tt>. As mentioned above, this typically happens
|
||||
automatically when you write a literal constant such as <tt>Enum::A</tt> in
|
||||
a context where a value of type <tt>Enum</tt> is expected. For example:
|
||||
</p>
|
||||
|
||||
<pre>void do_something(Enum value) { ... }
|
||||
|
||||
do_something(Enum::A); // converted silently</pre>
|
||||
|
||||
<a id="operator+"></a>
|
||||
<h3>
|
||||
<span>global unary constexpr</span> operator +(_Enumerated)
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
For use when the compiler does not choose the implicit constructor above.
|
||||
For example:
|
||||
</p>
|
||||
|
||||
<pre>(Enum::A).to_string()</pre>
|
||||
|
||||
<p>
|
||||
This expression does not compile because <tt>Enum::A</tt> is not an object,
|
||||
and the compiler does not promote it. The promotion can be forced:
|
||||
</p>
|
||||
|
||||
<pre>(+Enum::A).to_string()</pre>
|
||||
|
||||
<p>
|
||||
This is easier to maintain than writing out a call to the converting
|
||||
constructor:
|
||||
</p>
|
||||
|
||||
<pre>((Enum)Enum::A).to_string()</pre>
|
||||
|
||||
<a id="cast"></a>
|
||||
<h3>
|
||||
<span>casting constexpr</span> operator _Enumerated() <span>const</span>
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
Enables implicit conversion of <tt>Enum</tt> values down to
|
||||
<tt>_Enumerated</tt>. The only purpose of this is to make <tt>Enum</tt>
|
||||
values directly usable in <tt>switch</tt> statements for compiler-supported
|
||||
case checking:
|
||||
</p>
|
||||
|
||||
<pre>switch(enum_value) {
|
||||
case Enum::A: ...; break;
|
||||
case Enum::B: ...; break;
|
||||
case Enum::C: ...; break;
|
||||
}</pre>
|
||||
|
||||
<p>
|
||||
It is, unfortunately, a hole in the type safety of <tt>Enum</tt>, since it
|
||||
allows implicit conversions to integral types (<tt>Enum</tt> to
|
||||
<tt>_Enumerated</tt>, then <tt>_Enumerated</tt> to an integer). The user
|
||||
should not rely on such conversions. They will probably be eliminated in the
|
||||
future, perhaps by replacing this conversion with a conversion to an
|
||||
<tt>enum class</tt>.
|
||||
</p>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
|
||||
<a id="integral"></a>
|
||||
<h2>
|
||||
<a href="#integral">Underlying integral type</a>
|
||||
<a href="#index">index</a>
|
||||
</h2>
|
||||
|
||||
<a id="typename-integral"></a>
|
||||
<h3><span>typename</span> Enum::_Integral</h3>
|
||||
|
||||
<p>
|
||||
An alias for the underlying type that <tt>Enum</tt> was declared with. For
|
||||
example, if the declaration is
|
||||
</p>
|
||||
|
||||
<pre>ENUM(Enum, uint32_t, A, B, C);</pre>
|
||||
|
||||
<p>
|
||||
Then <tt>Enum::_Integral</tt> is the same as <tt>uint32_t</tt>.
|
||||
</p>
|
||||
|
||||
<a id="from_integral"></a>
|
||||
<h3><span>static constexpr</span> Enum _from_integral(_Integral)</h3>
|
||||
|
||||
<p>
|
||||
Checked cast from a numeric value to an enum value. The function checks that
|
||||
there is an enum constant with the given value. If not, it <em>throws</em>
|
||||
<tt>std::runtime_error</tt>. The check takes time <em>linear</em> in the
|
||||
number of constants in <tt>Enum</tt>.
|
||||
</p>
|
||||
|
||||
<a id="from_integral_unchecked"></a>
|
||||
<h3>
|
||||
<span>static constexpr</span> Enum _from_integral_unchecked(_Integral)
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
Unchecked cast from a numeric value to an enum value. The function assumes
|
||||
that there is an enum constant with the given value. The user has to ensure
|
||||
that this assumption holds. If not, the behavior of subsequent operations
|
||||
on the returned enum value is undefined.
|
||||
</p>
|
||||
|
||||
<a id="to_integral"></a>
|
||||
<h3>
|
||||
<span>member constexpr</span>
|
||||
_Integral enum_value.to_integral() <span>const</span>
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
Returns the numeric representation of an enum value.
|
||||
</p>
|
||||
|
||||
<a id="is_valid-integral"></a>
|
||||
<h3><span>static constexpr</span> bool _is_valid(_Integral)</h3>
|
||||
|
||||
<p>
|
||||
Checks that the given numeric value represents one of the constants in
|
||||
<tt>Enum</tt>, as in <tt>_from_integral</tt>. Complexity is <em>linear</em>
|
||||
in the number of constants.
|
||||
</p>
|
||||
|
||||
<h3><span>invariant </span> sizeof(Enum) == sizeof(Enum::_Integral)</h3>
|
||||
|
||||
<h3><span>invariant </span> alignof(Enum) == alignof(Enum::_Integral)</h3>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
|
||||
<a id="string"></a>
|
||||
<h2>
|
||||
<a href="#string">String conversions</a>
|
||||
<a href="#index">index</a>
|
||||
</h2>
|
||||
|
||||
<a id="from_string"></a>
|
||||
<h3><span>static constexpr</span> Enum _from_string(const char*)</h3>
|
||||
|
||||
<p>
|
||||
Returns the enum constant given by the string. For example:
|
||||
</p>
|
||||
|
||||
<pre>Enum::_from_string("A") == Enum::A</pre>
|
||||
|
||||
<p>
|
||||
Complexity is <em>linear</em> in the number of constants multiplied by the
|
||||
length of the longest constant name. If the string does not name a constant,
|
||||
<em>throws</em> <tt>std::runtime_error</tt>.
|
||||
</p>
|
||||
|
||||
<a id="from_string_nocase"></a>
|
||||
<h3><span>static constexpr</span> Enum _from_string_nocase(const char*)</h3>
|
||||
|
||||
<p>
|
||||
The same as above, but lookup is case-insensitive.
|
||||
</p>
|
||||
|
||||
<a id="to_string"></a>
|
||||
<h3>
|
||||
<span>member constexpr</span> const char* enum_value.to_string()
|
||||
<span>const</span>
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
Returns the string representation of enum value on which the method is
|
||||
called. If multiple constants have the same numeric value, the string
|
||||
returned can be the representation any of the constants. Complexity is
|
||||
<em>linear</em> in the number of constants. If the string does not name a
|
||||
constant, <em>throws</em> <tt>std::runtime_error</tt>.
|
||||
</p>
|
||||
|
||||
<a id="name"></a>
|
||||
<h3><span>static constexpr</span> const char *_name</h3>
|
||||
|
||||
<p>
|
||||
The name of the type, i.e., for
|
||||
</p>
|
||||
|
||||
<pre>ENUM(Enum, int, A, B, C);</pre>
|
||||
|
||||
<p>
|
||||
<tt>Enum::_name == "Enum"</tt>
|
||||
</p>
|
||||
|
||||
<a id="is_valid-string"></a>
|
||||
<h3><span>static constexpr</span> bool _is_valid(const char*)</h3>
|
||||
|
||||
<p>
|
||||
Checks that the given string is the name of one of the constants in
|
||||
<tt>Enum</tt>, as in <tt>_from_string</tt>, with the same complexity.
|
||||
</p>
|
||||
|
||||
<a id="is_valid_nocase"></a>
|
||||
<h3><span>static constexpr</span> bool _is_valid_nocase(const char*)</h3>
|
||||
|
||||
<p>
|
||||
The same as above, but corresponding to <tt>_from_string_nocase</tt>.
|
||||
</p>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
|
||||
<a id="iteration"></a>
|
||||
<h2>
|
||||
<a href="#iteration">Iteration</a>
|
||||
<a href="#index">index</a>
|
||||
</h2>
|
||||
|
||||
<a id="values"></a>
|
||||
<h3><span>static constexpr</span> _ValueIterable values</h3>
|
||||
|
||||
<p>
|
||||
An iterable container with all the defined constant values, in declaration
|
||||
order. Suitable for use with range-based <tt>for</tt> loops:
|
||||
</p>
|
||||
|
||||
<pre>for (Enum value : Enum::_values) {
|
||||
// Will iterate over Enum::A, Enum::B, Enum::C
|
||||
}</pre>
|
||||
|
||||
<h3><span>class</span> _ValueIterable::iterator</h3>
|
||||
|
||||
<p>
|
||||
An iterator over defined constant values with a <tt>constexpr</tt>
|
||||
dereference operator. Can be created explicitly by the <tt>constexpr</tt>
|
||||
expressions
|
||||
</p>
|
||||
|
||||
<pre>Enum::_values::begin()
|
||||
Enum::_values::end()</pre>
|
||||
|
||||
<a id="names"></a>
|
||||
<h3><span>static constexpr</span> _NameIterable names</h3>
|
||||
|
||||
<p>
|
||||
An iterable container with the names of the defined constant values, in
|
||||
declaration order. Suitable for use with range-based <tt>for</tt> loops:
|
||||
</p>
|
||||
|
||||
<pre>for (const char *name : Enum::_names) {
|
||||
// Will iterate over "A", "B", "C"
|
||||
}</pre>
|
||||
|
||||
<h3><span>class</span> _NameIterable::iterator</h3>
|
||||
|
||||
<p>
|
||||
An iterator over defined constant names with a <tt>constexpr</tt>
|
||||
dereference operator. Can be created explicitly by the <tt>constexpr</tt>
|
||||
expressions
|
||||
</p>
|
||||
|
||||
<pre>Enum::_names::begin()
|
||||
Enum::_names::end()</pre>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
|
||||
<a id="range"></a>
|
||||
<h2>
|
||||
<a href="#range">Range properties</a>
|
||||
<a href="#index">index</a>
|
||||
</h2>
|
||||
|
||||
<a id="first"></a>
|
||||
<h3><span>static constexpr</span> _Enumerated _first</h3>
|
||||
|
||||
<p>
|
||||
The first defined constant. For example, in
|
||||
</p>
|
||||
|
||||
<pre>ENUM(Enum, int, A = 1, B = 5, C = 0);</pre>
|
||||
|
||||
<p><tt>Enum::_first == Enum::A</tt></p>
|
||||
|
||||
<a id="last"></a>
|
||||
<h3><span>static constexpr</span> _Enumerated _last</h3>
|
||||
|
||||
<p>
|
||||
The last defined constant, i.e. <tt>Enum::C</tt> in the above example.
|
||||
</p>
|
||||
|
||||
<a id="min"></a>
|
||||
<h3><span>static constexpr</span> _Enumerated _min</h3>
|
||||
|
||||
<p>
|
||||
The defined constant with the smallest numeric value, i.e. <tt>Enum::C</tt>
|
||||
in the above example.
|
||||
</p>
|
||||
|
||||
<a id="max"></a>
|
||||
<h3><span>static constexpr</span> _Enumerated _max</h3>
|
||||
|
||||
<p>
|
||||
The defined constant with the greatest numeric value, i.e. <tt>Enum::B</tt>
|
||||
in the above example.
|
||||
</p>
|
||||
|
||||
<a id="size"></a>
|
||||
<h3><span>static constexpr</span> size_t _size</h3>
|
||||
|
||||
<p>
|
||||
The number of constants defined, i.e. 3 in the above example.
|
||||
</p>
|
||||
|
||||
<a id="span"></a>
|
||||
<h3><span>static constexpr</span> _Integral _span</h3>
|
||||
|
||||
<p>
|
||||
The numeric span of the constants defined, i.e. <tt>_max - _min + 1</tt>,
|
||||
which is 6 in the above example.
|
||||
</p>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
|
||||
<a id="comparison"></a>
|
||||
<h2>
|
||||
<a href="#comparison">Comparisons</a>
|
||||
<a href="#index">index</a>
|
||||
</h2>
|
||||
|
||||
<h3>
|
||||
<span>member constexpr</span> bool operator ==(const Enum&)
|
||||
<span>const</span>
|
||||
</h3>
|
||||
|
||||
<h3>
|
||||
<span>member constexpr</span> bool operator ==(_Enumerated)
|
||||
<span>const</span>
|
||||
</h3>
|
||||
|
||||
<h3>
|
||||
<span>member constexpr</span> bool operator !=(const Enum&)
|
||||
<span>const</span>
|
||||
</h3>
|
||||
|
||||
<h3>
|
||||
<span>member constexpr</span> bool operator !=(_Enumerated)
|
||||
<span>const</span>
|
||||
</h3>
|
||||
|
||||
<h3>
|
||||
<span>member constexpr</span> bool operator <(const Enum&)
|
||||
<span>const</span>
|
||||
</h3>
|
||||
|
||||
<h3>
|
||||
<span>member constexpr</span> bool operator <(_Enumerated)
|
||||
<span>const</span>
|
||||
</h3>
|
||||
|
||||
<h3>
|
||||
<span>member constexpr</span> bool operator <=(const Enum&)
|
||||
<span>const</span>
|
||||
</h3>
|
||||
|
||||
<h3>
|
||||
<span>member constexpr</span> bool operator <=(_Enumerated)
|
||||
<span>const</span>
|
||||
</h3>
|
||||
|
||||
<h3>
|
||||
<span>member constexpr</span> bool operator >(const Enum&)
|
||||
<span>const</span>
|
||||
</h3>
|
||||
|
||||
<h3>
|
||||
<span>member constexpr</span> bool operator >(_Enumerated)
|
||||
<span>const</span>
|
||||
</h3>
|
||||
|
||||
<h3>
|
||||
<span>member constexpr</span> bool operator >=(const Enum&)
|
||||
<span>const</span>
|
||||
</h3>
|
||||
|
||||
<h3>
|
||||
<span>member constexpr</span> bool operator >=(_Enumerated)
|
||||
<span>const</span>
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
These define an ordering on values of types <tt>Enum</tt> and
|
||||
<tt>Enum::_Enumerated</tt>. The ordering is according to the values'
|
||||
numeric representations. That means that two values that have been aliased
|
||||
will compare equal. Direct comparisons with all other types are forbidden;
|
||||
this is enforced by deleted comparison operators for all other types.
|
||||
</p>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
|
||||
<a id="safety"></a>
|
||||
<h2>
|
||||
<a href="#safety">Additional type safety</a>
|
||||
<a href="#index">index</a>
|
||||
</h2>
|
||||
|
||||
<h3><span>default constructor</span> Enum() = delete</h3>
|
||||
|
||||
<p>
|
||||
The default constructor is deleted to encourage initialization with valid
|
||||
values only and to avoid undefined states. See
|
||||
<a href="https://github.com/aantron/better-enums/blob/master/example/6-traits.cc">
|
||||
example/6-traits.cc</a> for an example of how to add an explicit notion of
|
||||
default value.
|
||||
</p>
|
||||
|
||||
<h3><span>invariant</span> no arithmetic</h3>
|
||||
|
||||
<p>Arithmetic operators are explicitly deleted.</p>
|
||||
|
||||
<h3><span>invariant</span> no implicit conversion from integers</h3>
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
Copyright © 2015.
|
||||
Distributed under the BSD 2-clause license. See
|
||||
<a href="https://github.com/aantron/better-enums/blob/master/LICENSE">
|
||||
LICENSE</a>.
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
|
||||
var property_headers = document.querySelectorAll("h3");
|
||||
for (var index = 0; index < property_headers.length; ++index) {
|
||||
var br = document.createElement("br");
|
||||
|
||||
var header = property_headers[index];
|
||||
var next = header.nextSibling;
|
||||
|
||||
if (next === null)
|
||||
header.parentNode.appendChild(br);
|
||||
else
|
||||
header.parentNode.insertBefore(br, next);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
279
doc/better-enums.css
Normal file
279
doc/better-enums.css
Normal file
@ -0,0 +1,279 @@
|
||||
body {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
pre, code, samp {
|
||||
font-family:
|
||||
Consolas, "Liberation Mono", Menlo, "Courier New", Courier, monospace;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #477093;
|
||||
padding: 1.5em 20px;
|
||||
border-radius: 5px;
|
||||
overflow: scroll;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
pre em {
|
||||
font-style: normal;
|
||||
text-shadow: 0 -1px grey;
|
||||
color: white;
|
||||
}
|
||||
|
||||
pre.comment {
|
||||
background-color: #EEF4F9;
|
||||
color: #888;
|
||||
border: 1px dashed #477093;
|
||||
}
|
||||
|
||||
pre.comment em {
|
||||
color: #333;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
code, samp {
|
||||
background-color: #EEF4F9;
|
||||
padding: 1px 3px;
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
nav > *, header > *, main, footer {
|
||||
max-width: 760px;
|
||||
padding-left: 230px;
|
||||
}
|
||||
|
||||
@media(max-width: 1240px) {
|
||||
nav > *, header > *, main > *, footer {
|
||||
padding-left: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: 1060px) {
|
||||
nav > *, header > *, main > *, footer {
|
||||
padding-left: 100px;
|
||||
padding-right: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: 900px) {
|
||||
nav > *, header > *, main > *, footer {
|
||||
padding-left: 50px;
|
||||
padding-right: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: 680px) {
|
||||
nav > *, header > *, main > *, footer {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
|
||||
border-bottom: 1px solid #68a;
|
||||
color: white;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
nav, .spacer {
|
||||
padding: 0.75em 0;
|
||||
font-size: 14px;
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
nav a {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
nav a.first {
|
||||
margin-left: 0;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #4C6F8C;
|
||||
background: linear-gradient(#395E7E, #4A79A0);
|
||||
color: white;
|
||||
padding: 50px 0;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 60px;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
header > h2 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 100;
|
||||
position: relative;
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
header > h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
position: relative;
|
||||
left: 4px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 2em;
|
||||
font-size: 36px;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
footer {
|
||||
font-size: 14px;
|
||||
margin-top: 150px;
|
||||
margin-bottom: 20px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
a[href=""] {
|
||||
color: white !important;
|
||||
background-color: red !important;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
header a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
main a[href], footer a[href] {
|
||||
background-color: #edd;
|
||||
color: #844;
|
||||
letter-spacing: -0.5px;
|
||||
padding: 0 2px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
header a[href], nav a[href] {
|
||||
color: inherit;
|
||||
background-color: transparent;
|
||||
letter-spacing: inherit;
|
||||
}
|
||||
|
||||
a[href] code {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
span.cpp, span.cc {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
span.eleven {
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
span#note:target {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
.pane {
|
||||
float: left;
|
||||
width: 49%;
|
||||
}
|
||||
|
||||
.hack {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.pane.left > * {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.pane.right > * {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
main > h3 {
|
||||
font-size: 30px;
|
||||
font-weight: 100;
|
||||
margin-top: 2em;
|
||||
color: black;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
.pane pre {
|
||||
font-size: 14px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
header {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div.back {
|
||||
position: absolute;
|
||||
bottom: -0.2em;
|
||||
left: -40px;
|
||||
font-size: 288px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 20px;
|
||||
opacity: 0.1;
|
||||
text-shadow: -20px 20px #444;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.panes {
|
||||
clear: both;
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.tutorial-footer {
|
||||
margin-top: 4em;
|
||||
}
|
||||
|
||||
.tutorial-footer.next {
|
||||
font-weight: 100;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.tutorial-footer.next a[href] {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-top: 5px;
|
||||
}
|
||||
128
doc/demo/101-special-values.md
Normal file
128
doc/demo/101-special-values.md
Normal file
@ -0,0 +1,128 @@
|
||||
## Special values
|
||||
|
||||
Suppose your project has a convention where each enum has special *invalid* and
|
||||
*default* values. With Better Enums, you can encode that directly at compile
|
||||
time, and then access each enum's special values using syntax like
|
||||
`Channel c = default_` and `Channel c = invalid`. This can make your code adapt
|
||||
automatically to changes in enum definitions, as well as make it easier to read
|
||||
and understand its intent.
|
||||
|
||||
---
|
||||
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
<em>#include <enum.h></em>
|
||||
|
||||
### Invalid
|
||||
|
||||
Perhaps the invalid value is usually called `Invalid`, but not in all enums. You
|
||||
can encode that using a function template for the common case, and a macro that
|
||||
creates specializations:
|
||||
|
||||
<em>template</em> <<em>typename Enum</em>>
|
||||
constexpr Enum find_invalid() { return Enum::Invalid; }
|
||||
|
||||
<em>#define OVERRIDE_INVALID</em>(<em>Enum</em>, <em>Value</em>) \
|
||||
template<> \
|
||||
constexpr Enum <em>find_invalid<Enum></em>() { return <em>Enum::Value</em>; }
|
||||
|
||||
Now, you can declare enums like these:
|
||||
|
||||
ENUM(<em>Channel</em>, int, Red, Green, Blue, <em>Invalid</em>)
|
||||
|
||||
ENUM(<em>Compression</em>, int, <em>Undefined</em>, None, Huffman)
|
||||
<em>OVERRIDE_INVALID</em>(<em>Compression</em>, <em>Undefined</em>)
|
||||
|
||||
and use them:
|
||||
|
||||
static_assert(<em>find_invalid</em><<em>Channel</em>>() == <em>+Channel::Invalid</em>, "");
|
||||
static_assert(<em>find_invalid</em><<em>Compression</em>>() == <em>+Compression::Undefined</em>, "");
|
||||
|
||||
This even supports enums that don't have an invalid value at all. As long as
|
||||
they don't have a constant called `Invalid`, you will get a compile-time error
|
||||
if you try to call `invalid()` on them — as you probably should!
|
||||
|
||||
### Default
|
||||
|
||||
To encode the policy on default values, we need to do a compile-time check that
|
||||
the first value is not invalid. Otherwise, the technique is the same.
|
||||
|
||||
template <typename Enum>
|
||||
constexpr Enum find_default()
|
||||
{
|
||||
return
|
||||
Enum::_size < 2 ?
|
||||
throw std::logic_error("enum has no valid constants") :
|
||||
Enum::_values()[0] == find_invalid<Enum>() ?
|
||||
Enum::_values()[1] :
|
||||
Enum::_values()[0];
|
||||
}
|
||||
|
||||
#define OVERRIDE_DEFAULT(Enum, Value) \
|
||||
static_assert(Enum::Value != Enum::Invalid, \
|
||||
#Enum ": default cannot equal invalid"); \
|
||||
template<> \
|
||||
constexpr Enum find_default<Enum>() { return Enum::Value; }
|
||||
|
||||
Usage:
|
||||
|
||||
static_assert(find_default<Channel>() == +Channel::Red, "");
|
||||
static_assert(find_default<Compression>() == +Compression::None, "");
|
||||
|
||||
And, if you do
|
||||
|
||||
ENUM(Answer, int, Yes, No, Invalid)
|
||||
// OVERRIDE_DEFAULT(Answer, Invalid)
|
||||
|
||||
you will get a helpful compile-time error saying
|
||||
`Answer: default cannot equal invalid`.
|
||||
|
||||
### Making the syntax nicer
|
||||
|
||||
For the final touch, we will make the syntax better by introducing new
|
||||
"keywords" called `default_` and `invalid` in such a way that we cause the
|
||||
compiler to do type inference:
|
||||
|
||||
template <typename Enum>
|
||||
struct assert_enum {
|
||||
using check = typename Enum::_enumerated;
|
||||
using type = Enum;
|
||||
};
|
||||
|
||||
struct invalid_t {
|
||||
template <typename To>
|
||||
constexpr operator To() const { return find_invalid<To>(); }
|
||||
|
||||
template <typename To>
|
||||
constexpr To convert() const { return find_invalid<To>(); }
|
||||
};
|
||||
|
||||
struct default_t {
|
||||
template <typename To>
|
||||
constexpr operator To() const { return find_default<To>(); }
|
||||
};
|
||||
|
||||
constexpr invalid_t invalid{};
|
||||
constexpr default_t default_{};
|
||||
|
||||
static_assert(+Channel::Invalid == invalid, "");
|
||||
static_assert(+Compression::Undefined == invalid, "");
|
||||
|
||||
static_assert(+Channel::Red == default_, "");
|
||||
static_assert(+Compression::None == default_, "");
|
||||
|
||||
We can now have nice code such as this:
|
||||
|
||||
int main()
|
||||
{
|
||||
Channel channel = default_;
|
||||
std::cout << channel._to_string() << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
There are many possible variations of these policies, but I think most of them
|
||||
can be encoded in a reasonable fashion using the tools Better Enums provides.
|
||||
Enjoy!
|
||||
50
doc/demo/102-bitset.md
Normal file
50
doc/demo/102-bitset.md
Normal file
@ -0,0 +1,50 @@
|
||||
## Bit sets
|
||||
|
||||
If you want to use `std::bitset` or a similar library to use enums as keys into
|
||||
a bit set, you need to know the number of bits at compile time. You can easily
|
||||
automate this with Better Enums, even when constants are not declared in
|
||||
increasing order.
|
||||
|
||||
---
|
||||
|
||||
We simply need to find the maximum value of any given enum type.
|
||||
|
||||
#include <bitset>
|
||||
#include <enum.h>
|
||||
|
||||
template <typename Enum>
|
||||
constexpr Enum max_loop(Enum accumulator, size_t index)
|
||||
{
|
||||
return
|
||||
index >= Enum::_size ? accumulator :
|
||||
Enum::_values()[index] > accumulator ?
|
||||
max_loop<Enum>(Enum::_values()[index], index + 1) :
|
||||
max_loop<Enum>(accumulator, index + 1);
|
||||
}
|
||||
|
||||
template <typename Enum>
|
||||
constexpr Enum max()
|
||||
{
|
||||
return max_loop<Enum>(Enum::_values()[0], 1);
|
||||
}
|
||||
|
||||
And use that to declare a bit set template:
|
||||
|
||||
template <typename Enum>
|
||||
using EnumSet = std::bitset<max<Enum>()._to_integral() + 1>;
|
||||
|
||||
Then rest is straightforward. The only issue is that, in $cxx11, it is necessary
|
||||
to keep calling `to_integral` on the enums when passing them to `bitset`
|
||||
functions. You may want to implement a more enum-friendly bit set type, or
|
||||
overload unary `operator -`.
|
||||
|
||||
ENUM(Channel, int, Red, Green, Blue)
|
||||
ENUM(Depth, int, TrueColor = 1, HighColor = 0)
|
||||
|
||||
int main()
|
||||
{
|
||||
EnumSet<Channel> channels;
|
||||
EnumSet<Depth> depths;
|
||||
|
||||
return 0;
|
||||
}
|
||||
117
doc/demo/103-quine.md
Normal file
117
doc/demo/103-quine.md
Normal file
@ -0,0 +1,117 @@
|
||||
## Semi-quine
|
||||
|
||||
Let's make a Better Enum compose its own definition. It won't be literally as
|
||||
defined, since we will lose some information about initializers, but we will be
|
||||
able to preserve their numeric values. We will reserve the memory buffers at
|
||||
compile time.
|
||||
|
||||
There are actually better ways to do this than shown here. You could define a
|
||||
macro that expands to an `ENUM` declaration and also stringizes it. The point
|
||||
here is to show some of the reflective capabilities of Better Enums, so you can
|
||||
adapt them for cases where a macro is not sufficient.
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
|
||||
#ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
#define BETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
#endif
|
||||
|
||||
#include <enum.h>
|
||||
|
||||
#define HIGH_COLOR 0
|
||||
|
||||
ENUM(Channel, int, Red, Green, Blue)
|
||||
ENUM(Depth, int, TrueColor = 1, HighColor = HIGH_COLOR)
|
||||
|
||||
First, we need to be able to get the length of each definition above. We will
|
||||
assume that the underlying type is always `int`, and that the spacing convention
|
||||
is followed as above. This allows us to write:
|
||||
|
||||
constexpr size_t value_length(int n, int bound = 10, size_t digits = 1)
|
||||
{
|
||||
return
|
||||
n < bound ? digits : value_length(n, bound * 10, digits + 1);
|
||||
}
|
||||
|
||||
constexpr size_t string_length(const char *s, size_t index = 0)
|
||||
{
|
||||
return s[index] == '\0' ? index : string_length(s, index + 1);
|
||||
}
|
||||
|
||||
template <typename Enum>
|
||||
constexpr size_t constants_length(size_t index = 0, size_t accumulator = 0)
|
||||
{
|
||||
return
|
||||
index >= Enum::_size ? accumulator :
|
||||
|
||||
constants_length<Enum>(
|
||||
index + 1, accumulator
|
||||
+ string_length(", ")
|
||||
+ string_length(Enum::_names()[index])
|
||||
+ string_length(" = ")
|
||||
+ value_length(
|
||||
Enum::_values()[index]._to_integral()));
|
||||
}
|
||||
|
||||
template <typename Enum>
|
||||
constexpr size_t declaration_length()
|
||||
{
|
||||
return
|
||||
string_length("ENUM(")
|
||||
+ string_length(Enum::_name())
|
||||
+ string_length(", int")
|
||||
+ constants_length<Enum>()
|
||||
+ string_length(")");
|
||||
}
|
||||
|
||||
Now, we can declare:
|
||||
|
||||
char channel_definition[declaration_length<Channel>() + 1];
|
||||
char depth_definition[declaration_length<Depth>() + 1];
|
||||
|
||||
And finally, the formatting function:
|
||||
|
||||
template <typename Enum>
|
||||
size_t format(char *buffer)
|
||||
{
|
||||
size_t offset = 0;
|
||||
|
||||
offset += std::sprintf(buffer, "ENUM(%s, int", Enum::_name());
|
||||
|
||||
for (Enum value : Enum::_values()) {
|
||||
offset +=
|
||||
std::sprintf(buffer + offset,
|
||||
", %s = %i",
|
||||
value._to_string(), value._to_integral());
|
||||
}
|
||||
|
||||
offset += std::sprintf(buffer + offset, ")");
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
size_t channel_length = format<Channel>(channel_definition);
|
||||
assert(channel_length + 1 == sizeof(channel_definition));
|
||||
|
||||
size_t depth_length = format<Depth>(depth_definition);
|
||||
assert(depth_length + 1 == sizeof(depth_definition));
|
||||
|
||||
std::cout << channel_definition << std::endl;
|
||||
std::cout << depth_definition << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
This outputs:
|
||||
|
||||
~~~comment
|
||||
ENUM(Channel, int, Red = 0, Green = 1, Blue = 2)
|
||||
ENUM(Depth, int, TrueColor = 1, HighColor = 0)
|
||||
~~~
|
||||
|
||||
This does have the advantage of not depending on anything else defined in the
|
||||
program, which isn't as easy to achieve with stringization.
|
||||
184
doc/docs.py
Executable file
184
doc/docs.py
Executable file
@ -0,0 +1,184 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import glob
|
||||
import re
|
||||
import shutil
|
||||
import string
|
||||
import transform
|
||||
import os
|
||||
import os.path
|
||||
|
||||
TEMPLATE_DIRECTORY = "template"
|
||||
OUTPUT_DIRECTORY = "html"
|
||||
TUTORIAL_DIRECTORY = "tutorial"
|
||||
DEMO_DIRECTORY = "demo"
|
||||
CXX_EXTENSION = "cc"
|
||||
|
||||
templates = {}
|
||||
|
||||
def load_templates():
|
||||
listing = os.listdir(TEMPLATE_DIRECTORY)
|
||||
|
||||
for file in listing:
|
||||
title = os.path.splitext(file)[0]
|
||||
stream = open(os.path.join(TEMPLATE_DIRECTORY, file))
|
||||
|
||||
try:
|
||||
templates[title] = stream.read()
|
||||
finally:
|
||||
stream.close()
|
||||
|
||||
def apply_template(template, args = {}, lax = False, **kwargs):
|
||||
if not lax:
|
||||
return string.Template(template).substitute(args, **kwargs)
|
||||
else:
|
||||
return string.Template(template).safe_substitute(args, **kwargs)
|
||||
|
||||
def scrub_comments(text):
|
||||
return re.sub("<!--([^-]|-[^-]|--[^>])*(-->|$)", "",
|
||||
text, flags = re.DOTALL)
|
||||
|
||||
def path_to_html(relative_path):
|
||||
return os.path.splitext(relative_path)[0] + ".html"
|
||||
|
||||
def path_to_md(relative_path):
|
||||
return os.path.splitext(relative_path)[0] + ".md"
|
||||
|
||||
def path_to_output(relative_path):
|
||||
path = os.path.join(OUTPUT_DIRECTORY, templates["version"], relative_path)
|
||||
|
||||
directory = os.path.dirname(path)
|
||||
if not os.path.lexists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
return path
|
||||
|
||||
def remove_output_directory():
|
||||
path = os.path.join(OUTPUT_DIRECTORY, templates["version"])
|
||||
if os.path.lexists(path):
|
||||
shutil.rmtree(path)
|
||||
|
||||
def compose_page(relative_path, definitions):
|
||||
definitions.update(templates)
|
||||
|
||||
html_file = path_to_html(relative_path)
|
||||
|
||||
if html_file == "index.html":
|
||||
canonical = templates["location"]
|
||||
definitions["title"] = \
|
||||
definitions["project"] + " - " + definitions["title"]
|
||||
else:
|
||||
canonical = os.path.join(templates["location"], "current", html_file)
|
||||
|
||||
prefix = re.sub("[^/]+", r"..", os.path.split(relative_path)[0])
|
||||
if len(prefix) > 0:
|
||||
prefix += "/"
|
||||
|
||||
definitions["canonical"] = canonical
|
||||
definitions["prefix"] = prefix
|
||||
|
||||
text = templates["page"]
|
||||
text = scrub_comments(text)
|
||||
|
||||
while '$' in text:
|
||||
text = apply_template(text, definitions)
|
||||
text = scrub_comments(text)
|
||||
|
||||
text = "<!-- Automatically generated - edit the templates! -->\n\n" + text
|
||||
|
||||
return text
|
||||
|
||||
def write_page(relative_path, text):
|
||||
html_file = path_to_html(relative_path)
|
||||
path = path_to_output(html_file)
|
||||
|
||||
stream = open(path, "w")
|
||||
try:
|
||||
stream.write(text)
|
||||
finally:
|
||||
stream.close()
|
||||
|
||||
def copy_static_file(relative_path):
|
||||
output_path = path_to_output(relative_path)
|
||||
shutil.copy(relative_path, output_path)
|
||||
|
||||
def read_extended_markdown(relative_path):
|
||||
md_file = path_to_md(relative_path)
|
||||
|
||||
stream = open(md_file)
|
||||
try:
|
||||
text = stream.read()
|
||||
return transform.to_html(text)
|
||||
finally:
|
||||
stream.close()
|
||||
|
||||
def compose_general_page(relative_path):
|
||||
definitions = read_extended_markdown(relative_path)
|
||||
text = compose_page(relative_path, definitions)
|
||||
write_page(relative_path, text)
|
||||
|
||||
def list_general_pages():
|
||||
return glob.glob("*.md")
|
||||
|
||||
def process_threaded(directory):
|
||||
sources = glob.glob(os.path.join(directory, "*.md"))
|
||||
|
||||
contents = []
|
||||
for file in sources:
|
||||
definitions = read_extended_markdown(file)
|
||||
|
||||
title_components = re.split("[ -]+", definitions["title"])
|
||||
title_components = map(lambda s: s.capitalize(), title_components)
|
||||
html_title = "".join(title_components)
|
||||
html_title = filter(lambda c: c not in ",!:-", html_title)
|
||||
html_file = os.path.join(directory, html_title + ".html")
|
||||
|
||||
source_file = \
|
||||
os.path.splitext(os.path.basename(file))[0] + "." + CXX_EXTENSION
|
||||
source_link = "$repo/blob/$version/example/" + source_file
|
||||
|
||||
definitions[directory + "_body"] = definitions["body"]
|
||||
definitions["body"] = templates[directory]
|
||||
definitions["source"] = source_link
|
||||
|
||||
contents.append((definitions["title"], html_file, definitions))
|
||||
|
||||
index = 0
|
||||
for title, html_file, definitions in contents:
|
||||
if index < len(contents) - 1:
|
||||
next_title, next_file, _ = contents[index + 1]
|
||||
definitions["computed_next"] = templates["next"]
|
||||
definitions["next_link"] = next_file
|
||||
definitions["next_title"] = next_title
|
||||
else:
|
||||
definitions["computed_next"] = templates["last"]
|
||||
|
||||
text = compose_page(html_file, definitions)
|
||||
write_page(html_file, text)
|
||||
|
||||
index += 1
|
||||
|
||||
text = ""
|
||||
for title, html_file, _ in contents:
|
||||
item = apply_template(templates["tocitem"], {}, True,
|
||||
link = html_file, title = title)
|
||||
text += item + "\n"
|
||||
|
||||
templates[directory + "_toc"] = text
|
||||
|
||||
def main():
|
||||
load_templates()
|
||||
|
||||
remove_output_directory()
|
||||
|
||||
process_threaded(TUTORIAL_DIRECTORY)
|
||||
process_threaded(DEMO_DIRECTORY)
|
||||
|
||||
general_pages = list_general_pages()
|
||||
for page in general_pages:
|
||||
compose_general_page(page)
|
||||
|
||||
copy_static_file("better-enums.css")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
257
doc/index.html
257
doc/index.html
@ -1,257 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<link rel="canonical" href="http://aantron.github.io/better-enums" />
|
||||
|
||||
<title>Better Enums - Clean Reflective Enums for C++</title>
|
||||
|
||||
<meta name="description" content="Clean, reflective enums for C++11. Conversions
|
||||
from/to string, constant list, and compile-time operations. Natural syntax
|
||||
and a high degree of type safety. Generator-free in typical usage. The
|
||||
library is header-only and easy to install. Since most operations are
|
||||
constexpr, they can be used in your own compile-time code. The library
|
||||
depends only on the standard library and one type of compiler extension. It
|
||||
is therefore almost entirely standard C++. This page is a tutorial." />
|
||||
|
||||
<meta name="author" content="Anton Bachin" />
|
||||
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<div>
|
||||
<a class="first"
|
||||
href="https://github.com/aantron/better-enums/releases/latest">
|
||||
Download
|
||||
</a>
|
||||
<a href="https://github.com/aantron/better-enums">GitHub</a>
|
||||
<a href="#tutorial">Tutorial</a>
|
||||
<a href="https://github.com/aantron/better-enums/tree/master/example">
|
||||
Samples
|
||||
</a>
|
||||
<a href="api.html">Reference</a>
|
||||
<a href="mailto:antonbachin@yahoo.com">Contact</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header>
|
||||
<h1>Better Enums</h1>
|
||||
<h2 class="tagline">Clean compile-time reflective enums for
|
||||
<span class="cpp">C++</span><span class="eleven">11</span></h2>
|
||||
<h3>— <span class="cpp">C++</span><span class="eleven">98</span> support coming soon without the "compile-time" :)</h3>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section>
|
||||
<p>
|
||||
Have you noticed the awkward situation with enums in
|
||||
<span class="cpp">C++</span>? Built-in <code>enum class</code> is missing
|
||||
basic features, such as string conversion. There are several approaches to
|
||||
address this, but most seem to involve unnatural syntax or code
|
||||
repetition. See some <a href="http://stackoverflow.com/questions/201593/is-there-a-simple-script-to-convert-c-enum-to-string">here</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Better Enums gives you rich, reflective enums with the nicest syntax yet
|
||||
seen.<sup><a href="#note">1</a></sup> All you have to do is add one short
|
||||
and simple header file to your project, and you are ready to go.
|
||||
</p>
|
||||
|
||||
<pre>#include <enum.h>
|
||||
ENUM(Channel, int, Red, Green, Blue);</pre>
|
||||
|
||||
<p>
|
||||
This gives you an <code>int</code>-sized enum type with all sorts of
|
||||
reflective capacity, including string conversions, value listing,
|
||||
compile-time operations, static information about the range of declared
|
||||
constants, and (last and probably least) the name <code>"Channel"</code>
|
||||
itself. You can even assign explicit values to constants and alias them
|
||||
like with a normal built-in <code>enum</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The enum is easy to maintain. There is no duplication of value names,
|
||||
no repetition of cumbersome macros, and no generator to run on every
|
||||
build. The library is header-only and has no dependencies, so there aren't
|
||||
any object files to link with. It is also standard
|
||||
<span class="cpp">C++</span>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Better Enums is free, available under the BSD license. The
|
||||
author is committed to further development and support, so please
|
||||
<a href="mailto:antonbachin@yahoo.com">contact me</a>, open an issue on
|
||||
<a href="https://github.com/aantron/better-enums">GitHub</a>, or ask a
|
||||
question on Stack Overflow.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Installation</h2>
|
||||
|
||||
<p>
|
||||
Download
|
||||
<a href="https://github.com/aantron/better-enums/blob/master/enum.h">
|
||||
enum.h</a> and copy it to your project. That's it! Just make sure it's in
|
||||
your include path and you are compiling with <span class="cc">gcc</span>
|
||||
or <span class="cc">clang</span> in
|
||||
<span class="cpp">C++</span><span class="eleven">11</span> mode.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="cc">msvc</span> support is coming in a near-future version
|
||||
with some enum features disabled. This is because
|
||||
<span class="cc">msvc</span>'s support for <code>constexpr</code> is
|
||||
lagging. The same patch will probably make it possible to use Better Enums
|
||||
with <span class="cpp">C++</span><span class="eleven">98</span>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 id="tutorial">Tutorial</h2>
|
||||
|
||||
<p>
|
||||
Create a file and put this code in it:
|
||||
</p>
|
||||
|
||||
<pre>#include <iostream>
|
||||
#include <enum.h>
|
||||
|
||||
ENUM(Channel, int, Red, Green, Blue);
|
||||
|
||||
int main()
|
||||
{
|
||||
Channel my_channel = Channel::Green;
|
||||
std::cout
|
||||
<< "Channel "
|
||||
<< my_channel.to_string()
|
||||
<< " has value "
|
||||
<< my_channel.to_integral()
|
||||
<< std::endl;
|
||||
|
||||
return 0;
|
||||
}</pre>
|
||||
|
||||
<p>
|
||||
Compile and run, and you should see the output
|
||||
<samp>Channel Green has value 1</samp>. Congratulations! You have compiled
|
||||
your first Better Enum.
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>
|
||||
Values are assigned to Better Enums just like to regular
|
||||
<span class="cpp">C++</span> enums. That means you can change the enum
|
||||
above to something like this:
|
||||
</p>
|
||||
|
||||
<pre>ENUM(Channel, int, Red, Green = 5, Blue, Last = Blue);</pre>
|
||||
|
||||
<p>
|
||||
and the result would be just as you'd expect: <code>Red</code> is 0,
|
||||
<code>Green</code> is 5, <code>Blue</code> is 6, and <code>Last</code> is
|
||||
also 6.
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>
|
||||
There is no need for <code>Last</code>, however. Every Better Enum comes
|
||||
with a built-in value called <code>_last</code>. So, if you have
|
||||
</p>
|
||||
|
||||
<pre>ENUM(Channel, int, Red, Green, Blue);</pre>
|
||||
|
||||
<p>
|
||||
Then <code>Channel::_last</code> is <code>Channel::Blue</code>! In fact,
|
||||
<code>Channel</code> also gets <code>_first</code>, <code>_min</code>,
|
||||
<code>_max</code>, <code>_span</code>, and <code>_size</code>. These
|
||||
built-in values have underscores so that you can define your own enums
|
||||
without having to worry about naming problems:
|
||||
</p>
|
||||
|
||||
<pre>ENUM(Position, int, first, last, other);</pre>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>
|
||||
You already saw how to convert an enum to a string or an integer. As you
|
||||
might guess, it is also possible to go the other way:
|
||||
<p>
|
||||
|
||||
<pre>Channel my_channel = Channel::_from_string("Blue");
|
||||
Channel my_channel = Channel::_from_integral(2);</pre>
|
||||
|
||||
<p>
|
||||
If your code tries to convert an invalid string or integer to a
|
||||
<code>Channel</code>, it will get an <code>std::runtime_error</code>
|
||||
exception.
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>
|
||||
You can iterate over all the declared values of an enum:
|
||||
</p>
|
||||
|
||||
<pre>for(Channel channel : Channel::_values)
|
||||
std::cout << channel.to_string() << std::endl;</pre>
|
||||
|
||||
<p>
|
||||
and directly over their names with <code>Channel::_names</code>.
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>
|
||||
Finally, all of the above can be done at compile time. This means you can
|
||||
do all sorts of parsing and processing at the same time the rest of your
|
||||
code is being compiled, improving runtime and startup performance! See
|
||||
some
|
||||
<a href="https://github.com/aantron/better-enums/blob/master/example/4-constexpr.cc">examples</a>
|
||||
<a href="https://github.com/aantron/better-enums/blob/master/example/6-traits.cc">2</a>
|
||||
<a href="https://github.com/aantron/better-enums/blob/master/example/8-constexpr-iterate.cc">3</a>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Where to go from here</h2>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/aantron/better-enums/releases/latest">
|
||||
Download Better Enums</a> and start using them in your project</li>
|
||||
<li>Browse the commented code
|
||||
<a href="https://github.com/aantron/better-enums/tree/master/example">
|
||||
samples</a>, including advanced usage</li>
|
||||
<li>Read the <a href="api.html">API documentation</a></li>
|
||||
<li style="display: none;">See results of <a href="performance.html">performance testing</a></li>
|
||||
<li>View, discuss, and contribute on
|
||||
<a href="https://github.com/aantron/better-enums">GitHub</a></li>
|
||||
<li>
|
||||
<a href="mailto:antonbachin@yahoo.com">Contact the author</a> directly
|
||||
— all feedback is welcome!
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
Copyright © 2015 Anton Bachin. Released under the BSD 2-clause license.
|
||||
See <a href="https://github.com/aantron/better-enums/blob/master/LICENSE">
|
||||
LICENSE</a>.
|
||||
<br />
|
||||
This page is part of the documentation for Better Enums 0.8.0.
|
||||
<br />
|
||||
<br />
|
||||
<span id="note"><sup>1</sup> Yet seen by the author, of course :)</span>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
125
doc/index.md
Normal file
125
doc/index.md
Normal file
@ -0,0 +1,125 @@
|
||||
Have you noticed the awkward situation with enums in $cxx? They are missing
|
||||
basic reflective features, such as string conversions. You are forced to put
|
||||
them through big `switch` statements and write duplicate enum names. It's a
|
||||
maintenance nightmare.
|
||||
|
||||
$be is a short header file that gives you rich, reflective enums, with the
|
||||
nicest syntax yet seen. Just include it, and you are ready to go. You get
|
||||
scoped, sized, printable, iterable enums that support initializers and still
|
||||
play nice with `switch` case checking!
|
||||
|
||||
<div class="panes">
|
||||
<div class="pane left">
|
||||
<h3>$cxx11</h3>
|
||||
|
||||
<pre>#include <iostream>
|
||||
<em>#include <enum.h></em>
|
||||
|
||||
<em>ENUM(Channel, int,
|
||||
Red = 1, Green, Blue)</em>
|
||||
|
||||
int main()
|
||||
{
|
||||
<em>Channel c = Channel::Red</em>;
|
||||
std::cout << <em>c._to_string()</em>;
|
||||
|
||||
<em>for (Channel c : Channel::_values())</em>
|
||||
std::cout << <em>c._to_string()</em>;
|
||||
|
||||
<em>switch (c)</em> {
|
||||
<em>case Channel::Red</em>:
|
||||
return <em>c._to_integral()</em>;
|
||||
<em>case Channel::Green</em>: return 15;
|
||||
<em>case Channel::Blue</em>: return 42;
|
||||
}
|
||||
}
|
||||
|
||||
<em>constexpr</em> Channel c =
|
||||
<em>Channel::_from_string("Blue")</em>;</pre>
|
||||
</div>
|
||||
<div class="pane right">
|
||||
<h3>$cxx98</h3>
|
||||
<pre>#include <iostream>
|
||||
<em>#include <enum.h></em>
|
||||
|
||||
<em>ENUM(Channel, int,
|
||||
Red = 1, Green, Blue)</em>
|
||||
|
||||
int main()
|
||||
{
|
||||
<em>Channel c = Channel::Red</em>;
|
||||
std::cout << <em>c._to_string()</em>;
|
||||
|
||||
for (size_t i = 0;
|
||||
<em>i < Channel::_size</em>; ++i) {
|
||||
|
||||
c = <em>Channel::_values()[i]</em>;
|
||||
std::cout << <em>c._to_string()</em>;
|
||||
}
|
||||
|
||||
<em>switch (c)</em> {
|
||||
<em>case Channel::Red</em>:
|
||||
return <em>c._to_integral()</em>;
|
||||
<em>case Channel::Green</em>: return 15;
|
||||
<em>case Channel::Blue</em>: return 42;
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="hack"></div>
|
||||
</div>
|
||||
|
||||
To install, simply <a $download>download enum.h</a> and add it to your project.
|
||||
|
||||
That's all. The library is header-only and has no dependencies. It is published
|
||||
under the BSD license, so you can do pretty much anything you want with it.
|
||||
|
||||
---
|
||||
|
||||
Better Enums is under active development and will always be supported. Follow
|
||||
the project on [GitHub]($repo) for updates.
|
||||
|
||||
---
|
||||
|
||||
<div class="panes">
|
||||
<div class="pane left">
|
||||
<h3>Tutorials</h3>
|
||||
<ol>
|
||||
$tutorial_toc
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="pane right">
|
||||
<h3>Advanced demos</h3>
|
||||
<ul>
|
||||
$demo_toc
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panes">
|
||||
<div class="pane left">
|
||||
<h3>Reference</h3>
|
||||
<ul>
|
||||
<li><a href="${prefix}ApiReference.html">API reference</a></li>
|
||||
<li><a href="${prefix}OptInFeatures.html">Opt-in features</a></li>
|
||||
<li>
|
||||
<a href="${prefix}ExtendingMacroLimits.html">Extending macro limits</a>
|
||||
</li>
|
||||
<li><a href="${prefix}CompilerSupport.html">Compiler support</a></li>
|
||||
<li><a href="${prefix}Performance.html">Performance</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hack"></div>
|
||||
|
||||
<!-- Contact -->
|
||||
|
||||
<!-- omfg -->
|
||||
|
||||
<!-- Development blurb -->
|
||||
|
||||
<!-- Prompts and such -->
|
||||
|
||||
%% title = Clean reflective enums for C++
|
||||
7
doc/performance.md
Normal file
7
doc/performance.md
Normal file
@ -0,0 +1,7 @@
|
||||
## Performance
|
||||
|
||||
This is a placeholder page — the only testing done so far shows that
|
||||
compiling a large (30+) enums on clang is faster than merely including
|
||||
`iostream`. More conclusive information coming today or in a few days! For the
|
||||
clang test, compilation of all those Better Enums takes about 60% of the time
|
||||
that processing `iostream` does.
|
||||
159
doc/style.css
159
doc/style.css
@ -1,159 +0,0 @@
|
||||
body {
|
||||
margin: 0;
|
||||
color: #555;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
pre, code, samp {
|
||||
font-family:
|
||||
Consolas, "Liberation Mono", Menlo, "Courier New", Courier, monospace;
|
||||
font-size: 16px;
|
||||
|
||||
color: white;
|
||||
text-shadow: 0 -1px grey;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #8AAECB;
|
||||
padding: 0.5em 20px;
|
||||
border-radius: 5px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
code, samp {
|
||||
background-color: #93B2CB;
|
||||
padding: 1px 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
nav > *, header > *, main > *, footer {
|
||||
max-width: 760px;
|
||||
padding-left: 230px;
|
||||
}
|
||||
|
||||
@media(max-width: 1240px) {
|
||||
nav > *, header > *, main > *, footer {
|
||||
padding-left: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: 1060px) {
|
||||
nav > *, header > *, main > *, footer {
|
||||
padding-left: 100px;
|
||||
padding-right: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: 900px) {
|
||||
nav > *, header > *, main > *, footer {
|
||||
padding-left: 50px;
|
||||
padding-right: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: 680px) {
|
||||
nav > *, header > *, main > *, footer {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
padding: 0.75em 0;
|
||||
background-color: #222;
|
||||
border-bottom: 1px solid #68a;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
nav a {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
nav a.first {
|
||||
margin-left: 0;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #4C6F8C;
|
||||
background: linear-gradient(#4C6F8C, #769DBD);
|
||||
color: white;
|
||||
padding: 50px 0;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 60px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
header > h2 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 300;
|
||||
position: relative;
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
header > h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
position: relative;
|
||||
left: 4px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 500;
|
||||
margin-top: 3em;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
footer {
|
||||
font-size: 16px;
|
||||
margin-top: 150px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
a {
|
||||
background-color: red;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a[href] {
|
||||
background-color: #D07A6F;
|
||||
color: white;
|
||||
padding: 0 2px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
nav a[href] {
|
||||
color: inherit;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
span.cpp, span.cc {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
span.eleven {
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
span#note:target {
|
||||
background-color: yellow;
|
||||
}
|
||||
1
doc/template/be.html
vendored
Normal file
1
doc/template/be.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
<span class="self">Better Enums</span>
|
||||
1
doc/template/cxx.tmpl
vendored
Normal file
1
doc/template/cxx.tmpl
vendored
Normal file
@ -0,0 +1 @@
|
||||
<span class="cpp">C++</span>
|
||||
1
doc/template/cxx11.tmpl
vendored
Normal file
1
doc/template/cxx11.tmpl
vendored
Normal file
@ -0,0 +1 @@
|
||||
<span class="cpp">C++</span><span class="eleven">11</span>
|
||||
1
doc/template/cxx98.tmpl
vendored
Normal file
1
doc/template/cxx98.tmpl
vendored
Normal file
@ -0,0 +1 @@
|
||||
<span class="cpp">C++</span><span class="eleven">98</span>
|
||||
6
doc/template/demo.tmpl
vendored
Normal file
6
doc/template/demo.tmpl
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
<p>
|
||||
The code on this page is an advanced demo of Better Enums. You can
|
||||
<a href="$source">download</a> it and try it out.
|
||||
</p>
|
||||
|
||||
$demo_body
|
||||
2
doc/template/download.tmpl
vendored
Normal file
2
doc/template/download.tmpl
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
href="https://raw.githubusercontent.com/aantron/better-enums/$ref/enum.h"
|
||||
download
|
||||
12
doc/template/footer.tmpl
vendored
Normal file
12
doc/template/footer.tmpl
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
Copyright © 2015 Anton Bachin. Released under the BSD 2-clause license.
|
||||
See <a href="https://github.com/aantron/better-enums/blob/master/LICENSE">
|
||||
LICENSE</a>.
|
||||
<br />
|
||||
This page is part of the documentation for Better Enums $version.
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
43
doc/template/header.tmpl
vendored
Normal file
43
doc/template/header.tmpl
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<title>$title</title>
|
||||
|
||||
<link rel="canonical" href="$canonical" />
|
||||
<!-- <meta name="description" content="$description" /> -->
|
||||
<meta name="author" content="Anton Bachin" />
|
||||
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
|
||||
<link rel="stylesheet" href="${prefix}better-enums.css" />
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<div>
|
||||
<a class="first" $download>Download</a>
|
||||
<a href="$repo">GitHub</a>
|
||||
<a href="${prefix}index.html">Home</a>
|
||||
<a href="${prefix}tutorial/HelloWorld.html">Tutorial</a>
|
||||
<!-- <a href="https://github.com/aantron/better-enums/tree/master/example">
|
||||
Samples
|
||||
</a> -->
|
||||
<!-- <a href="${prefix}api.html">Reference</a> -->
|
||||
<!-- <a href="mailto:antonbachin@yahoo.com">Contact</a> -->
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="spacer"> </div>
|
||||
|
||||
<header>
|
||||
<div class="back">{}</div>
|
||||
|
||||
<h1><a href="${prefix}index.html">Better Enums</a></h1>
|
||||
<h2>Fast, intuitive enums for $cxx</h2>
|
||||
<h3>Open-source under the BSD license</h3>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
6
doc/template/last.tmpl
vendored
Normal file
6
doc/template/last.tmpl
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
<section class="tutorial-footer">
|
||||
<p class="up">
|
||||
This is the last tutorial! Return to the
|
||||
<a href="${prefix}index.html">main page</a> for other resources.
|
||||
</p>
|
||||
</section>
|
||||
1
doc/template/location.tmpl
vendored
Normal file
1
doc/template/location.tmpl
vendored
Normal file
@ -0,0 +1 @@
|
||||
http://aantron.github.io/better-enums
|
||||
9
doc/template/next.tmpl
vendored
Normal file
9
doc/template/next.tmpl
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<section class="tutorial-footer">
|
||||
<p class="next">
|
||||
Continue to the next tutorial: <a href="$prefix$next_link">${next_title}</a>
|
||||
</p>
|
||||
|
||||
<p class="up">
|
||||
Or, return to the <a href="${prefix}index.html">home page</a>.
|
||||
</p>
|
||||
</section>
|
||||
5
doc/template/page.tmpl
vendored
Normal file
5
doc/template/page.tmpl
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
$header
|
||||
|
||||
$body
|
||||
|
||||
$footer
|
||||
1
doc/template/project.tmpl
vendored
Normal file
1
doc/template/project.tmpl
vendored
Normal file
@ -0,0 +1 @@
|
||||
Better Enums
|
||||
1
doc/template/ref.tmpl
vendored
Normal file
1
doc/template/ref.tmpl
vendored
Normal file
@ -0,0 +1 @@
|
||||
master
|
||||
1
doc/template/repo.tmpl
vendored
Normal file
1
doc/template/repo.tmpl
vendored
Normal file
@ -0,0 +1 @@
|
||||
https://github.com/aantron/better-enums
|
||||
1
doc/template/tocitem.tmpl
vendored
Normal file
1
doc/template/tocitem.tmpl
vendored
Normal file
@ -0,0 +1 @@
|
||||
<li><a href="${prefix}$link">$title</a></li>
|
||||
8
doc/template/tutorial.tmpl
vendored
Normal file
8
doc/template/tutorial.tmpl
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
<p>
|
||||
Welcome to the Better Enums tutorials! The code in this tutorial forms a
|
||||
valid program, which you can <a href="$source">download</a> and play with.
|
||||
</p>
|
||||
|
||||
$tutorial_body
|
||||
|
||||
$computed_next
|
||||
1
doc/template/version.tmpl
vendored
Normal file
1
doc/template/version.tmpl
vendored
Normal file
@ -0,0 +1 @@
|
||||
master
|
||||
197
doc/transform.py
Executable file
197
doc/transform.py
Executable file
@ -0,0 +1,197 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# Reads mixed source/text markdown files and outputs HTML and/or C++ source.
|
||||
# Usage:
|
||||
# ./transform.py --o-html OUT.html --o-cxx OUT.cc in.md
|
||||
|
||||
import argparse
|
||||
import mistune
|
||||
import re
|
||||
import sys
|
||||
|
||||
parser = argparse.ArgumentParser(description = "Translate markdown tutorials.",
|
||||
epilog = "At least one output file must be specified")
|
||||
parser.add_argument("--o-html", metavar = "HTML", dest = "html_file",
|
||||
help = "HTML output file name")
|
||||
parser.add_argument("--o-cxx", metavar = "CXX", dest = "cxx_file",
|
||||
help = "C++ output file name")
|
||||
parser.add_argument("md_file", metavar = "MD")
|
||||
|
||||
def pretty_print(text, prefix, start_with_prefix = True):
|
||||
words = text.split()
|
||||
|
||||
index = 0
|
||||
|
||||
if start_with_prefix:
|
||||
result = prefix
|
||||
else:
|
||||
result = ""
|
||||
|
||||
while index < len(words):
|
||||
column = len(prefix)
|
||||
|
||||
result += words[index]
|
||||
column += len(words[index])
|
||||
index += 1
|
||||
|
||||
while index < len(words) and column + 1 + len(words[index]) <= 80:
|
||||
result += " "
|
||||
result += words[index]
|
||||
column += 1 + len(words[index])
|
||||
index += 1
|
||||
|
||||
result += "\n"
|
||||
if index < len(words):
|
||||
result += prefix
|
||||
|
||||
return result
|
||||
|
||||
class HtmlRenderer(mistune.Renderer):
|
||||
def __init__(self):
|
||||
super(HtmlRenderer, self).__init__()
|
||||
self._definitions = {}
|
||||
|
||||
def header(self, text, level, raw = None):
|
||||
if level == 2:
|
||||
if "title" not in self._definitions:
|
||||
self._definitions["title"] = text
|
||||
|
||||
return super(HtmlRenderer, self).header(text, level, raw)
|
||||
|
||||
def paragraph(self, text):
|
||||
if text.startswith("%%"):
|
||||
tokens = text[2:].split("=", 1)
|
||||
if len(tokens) == 2:
|
||||
pass
|
||||
key = tokens[0].strip()
|
||||
value = tokens[1].strip()
|
||||
|
||||
self._definitions[key] = value
|
||||
|
||||
return ""
|
||||
|
||||
return super(HtmlRenderer, self).paragraph(text)
|
||||
|
||||
def block_code(self, code, lang):
|
||||
escaped = mistune.escape(re.sub("\n*$", "", code))
|
||||
replaced = re.sub("<em>", "<em>", escaped)
|
||||
replaced = re.sub("</em>", "</em>", replaced)
|
||||
replaced = re.sub("#(ifn?def|endif).*\n?", "", replaced)
|
||||
|
||||
if lang == "comment":
|
||||
start_tag = "<pre class=\"comment\">"
|
||||
else:
|
||||
start_tag = "<pre>"
|
||||
|
||||
return start_tag + replaced + "</pre>"
|
||||
|
||||
def definitions(self):
|
||||
return self._definitions
|
||||
|
||||
def to_html(text):
|
||||
renderer = HtmlRenderer()
|
||||
html = mistune.Markdown(renderer = renderer).render(text)
|
||||
definitions = renderer.definitions()
|
||||
definitions["body"] = html
|
||||
return definitions
|
||||
|
||||
class CxxRenderer(mistune.Renderer):
|
||||
def __init__(self):
|
||||
super(CxxRenderer, self).__init__()
|
||||
self._not_in_list()
|
||||
self._not_paragraph()
|
||||
|
||||
def header(self, text, level, raw = None):
|
||||
self._not_in_list()
|
||||
return self._join_paragraph() + pretty_print(text, "// ")
|
||||
|
||||
def paragraph(self, text):
|
||||
if text.startswith("%%"):
|
||||
return ""
|
||||
|
||||
self._not_in_list()
|
||||
return self._join_paragraph() + pretty_print(text, "// ")
|
||||
|
||||
def codespan(self, text):
|
||||
return text
|
||||
|
||||
def list(self, body, ordered = True):
|
||||
return self._join_paragraph() + body
|
||||
|
||||
def list_item(self, text):
|
||||
return ("// %i. " % self._number_list_item()) + \
|
||||
pretty_print(text, "// ", False)
|
||||
|
||||
def block_code(self, code, lang):
|
||||
self._not_in_list()
|
||||
|
||||
code = re.sub("</?em>", "", code)
|
||||
|
||||
if lang == "comment":
|
||||
code = re.sub("^(.)", "// \g<1>", code, flags = re.MULTILINE)
|
||||
code = re.sub("^$", "//", code, flags = re.MULTILINE)
|
||||
return self._join_paragraph() + code + "\n"
|
||||
else:
|
||||
self._not_paragraph()
|
||||
return "\n" + code
|
||||
|
||||
def hrule(self):
|
||||
self._not_in_list()
|
||||
self._not_paragraph()
|
||||
return ""
|
||||
|
||||
def footnote_ref(self, key, index):
|
||||
return ""
|
||||
|
||||
def footnotes(self, text):
|
||||
return ""
|
||||
|
||||
def _not_in_list(self):
|
||||
self._list_index = None
|
||||
|
||||
def _number_list_item(self):
|
||||
if self._list_index == None:
|
||||
self._list_index = 2
|
||||
return 1
|
||||
else:
|
||||
result = self._list_index
|
||||
self._list_index += 1
|
||||
return result
|
||||
|
||||
def _not_paragraph(self):
|
||||
self._join = False
|
||||
|
||||
def _paragraph(self):
|
||||
self._join = True
|
||||
|
||||
def _join_paragraph(self):
|
||||
if self._join:
|
||||
result = "//\n"
|
||||
else:
|
||||
result = ""
|
||||
|
||||
self._join = True
|
||||
|
||||
return result
|
||||
|
||||
def main(md_file, html_file, cxx_file):
|
||||
markdown = open(md_file, "r").read()
|
||||
|
||||
if html_file != None:
|
||||
html = mistune.markdown(markdown)
|
||||
open(html_file, "w").write(html)
|
||||
|
||||
if cxx_file != None:
|
||||
renderer = CxxRenderer()
|
||||
source = mistune.Markdown(renderer = renderer).render(markdown)
|
||||
source = re.sub(r"\n*$", "\n", source)
|
||||
source = "// This file was generated automatically\n\n" + source
|
||||
open(cxx_file, "w").write(source)
|
||||
|
||||
if __name__ == "__main__":
|
||||
arguments = parser.parse_args()
|
||||
if arguments.html_file == None and arguments.cxx_file == None:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
main(arguments.md_file, arguments.html_file, arguments.cxx_file)
|
||||
21
doc/tutorial/1-hello-world.md
Normal file
21
doc/tutorial/1-hello-world.md
Normal file
@ -0,0 +1,21 @@
|
||||
## Hello, World!
|
||||
|
||||
Download <a $download>enum.h</a>, then build this program with it:
|
||||
|
||||
#include <iostream>
|
||||
<em>#include "enum.h"</em>
|
||||
|
||||
<em>ENUM(Word, int, Hello, World)</em>
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << <em>(+Word::Hello)._to_string()</em> << ", "
|
||||
<< <em>(+Word::World)._to_string()</em> << "!"
|
||||
<< std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Run it, and you should see the output "Hello, World!"
|
||||
|
||||
Congratulations, you have just created your first Better Enum!
|
||||
157
doc/tutorial/2-conversions.md
Normal file
157
doc/tutorial/2-conversions.md
Normal file
@ -0,0 +1,157 @@
|
||||
## Conversions
|
||||
|
||||
Let's begin by including `enum.h` and declaring our enum:
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
<em>#include <enum.h></em>
|
||||
|
||||
<em>ENUM(Channel, int, Cyan = 1, Magenta, Yellow, Black)</em>
|
||||
|
||||
We now have an `int`-sized enum with four constants.
|
||||
|
||||
There are three groups of conversion functions: for strings, case-insensitive
|
||||
strings, and integers. They all follow the same pattern, so I'll explain the
|
||||
string functions in detail, and the rest can be understood by analogy.
|
||||
|
||||
### Strings
|
||||
|
||||
There are three functions:
|
||||
|
||||
1. `._to_string`
|
||||
2. `::_from_string`
|
||||
3. `::_from_string_nothrow`
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
<em>Channel channel = Channel::Cyan</em>;
|
||||
std::cout << <em>channel._to_string()</em> << " ";
|
||||
|
||||
As you'd expect, the code above prints "Cyan".
|
||||
|
||||
If `channel` is invalid — for example, if you simply cast the number "42"
|
||||
to `Channel` — then the result of `to_string` is undefined.
|
||||
|
||||
---
|
||||
|
||||
channel = <em>Channel::_from_string("Magenta")</em>;
|
||||
std::cout << channel._to_string() << " ";
|
||||
|
||||
This is also straightforward. If you pass a string which is not the name of a
|
||||
declared value, `_from_string` throws `std::runtime_error`.
|
||||
|
||||
---
|
||||
|
||||
If you don't want an exception, there is `_from_string_nothrow`:
|
||||
|
||||
<em>better_enums::optional<Channel></em> maybe_channel =
|
||||
<em>Channel::_from_string_nothrow("Yellow")</em>;
|
||||
|
||||
if (<em>!maybe_channel</em>)
|
||||
std::cout << "error";
|
||||
else
|
||||
std::cout << <em>maybe_channel-></em>_to_string() << " ";
|
||||
|
||||
This returns an *optional value*, in the style of
|
||||
[`boost::optional`](http://www.boost.org/doc/libs/1_58_0/libs/optional/doc/html/index.html)
|
||||
or the proposed
|
||||
[`std::optional`](http://en.cppreference.com/w/cpp/experimental/optional).
|
||||
|
||||
What that means for the above code is:
|
||||
|
||||
- if the conversion succeeds, `maybe_channel` converts to `true` and
|
||||
`*maybe_channel` is the converted value of type `Channel`,
|
||||
- if the conversion fails, `maybe_channel` converts to `false`.
|
||||
|
||||
In $cxx11, you can use `auto` to avoid writing out the optional type:
|
||||
|
||||
~~~comment
|
||||
<em>auto</em> maybe_channel = <em>Channel::_from_string_nothrow("Yellow")</em>;
|
||||
if (<em>!maybe_channel</em>)
|
||||
std::cout << "error";
|
||||
else
|
||||
std::cout << <em>maybe_channel-></em>_to_string() << " ";
|
||||
~~~
|
||||
|
||||
### Case-insensitive strings
|
||||
|
||||
The "`_nocase`" string conversions follow the same pattern, except for the lack
|
||||
of a "`to_string_nocase`".
|
||||
|
||||
1. `::_from_string_nocase`
|
||||
2. `::_from_string_nocase_nothrow`
|
||||
|
||||
|
||||
channel = <em>Channel::_from_string_nocase("cYaN")</em>;
|
||||
std::cout << channel._to_string() << " ";
|
||||
|
||||
maybe_channel = <em>Channel::_from_string_nocase_nothrow("rEeD")</em>;
|
||||
assert(!maybe_channel);
|
||||
|
||||
### Integers
|
||||
|
||||
And, it is similar with the *representation type* `int`:
|
||||
|
||||
1. `._to_integral`
|
||||
2. `::_from_integral`
|
||||
3. `::_from_integral_nothrow`
|
||||
4. `::_from_integral_unchecked`
|
||||
|
||||
|
||||
channel = Channel::Cyan;
|
||||
std::cout << <em>channel._to_integral()</em> << " ";
|
||||
|
||||
channel = <em>Channel::_from_integral(2)</em>;
|
||||
std::cout << channel._to_string() << " ";
|
||||
|
||||
maybe_channel = <em>Channel::_from_integral_nothrow(0)</em>;
|
||||
assert(<em>!maybe_channel</em>);
|
||||
|
||||
That prints "1 Magenta".
|
||||
|
||||
`_from_integral_unchecked` is a no-op unchecked cast of integers to enums, so
|
||||
use it carefully.
|
||||
|
||||
channel = <em>Channel::_from_integral_unchecked(0)</em>;
|
||||
// <em>Invalid</em> - better not to try converting it to string!
|
||||
|
||||
### Aside
|
||||
|
||||
You have certainly noticed that all the method names begin with underscores.
|
||||
This is because they share scope with the enum constants that you declare.
|
||||
Better Enums is trying to stay out of your way by using a prefix.
|
||||
|
||||
### Validity checking
|
||||
|
||||
For completeness, Better Enums also provides three validity checking functions,
|
||||
one for each of the groups of conversions — string, case-insensitive
|
||||
string, and integer:
|
||||
|
||||
assert(<em>Channel::_is_valid(3)</em>);
|
||||
assert(<em>Channel::_is_valid("Magenta")</em>);
|
||||
assert(<em>Channel::_is_valid_nocase("cYaN")</em>);
|
||||
|
||||
---
|
||||
|
||||
Almost done.
|
||||
|
||||
There is one unfortunate wrinkle. You cannot convert a literal constant such as
|
||||
`Channel::Cyan` directly to, for example, a string. You have to prefix it with
|
||||
`+`:
|
||||
|
||||
std::cout << (<em>+Channel::Cyan</em>)._to_string();
|
||||
|
||||
This is due to some type gymnastics in the implementation of Better Enums. The
|
||||
<a>Reference</a> section has a full explanation.
|
||||
|
||||
---
|
||||
|
||||
This concludes the first tutorial!
|
||||
|
||||
---
|
||||
|
||||
std::cout << std::endl;
|
||||
return 0;
|
||||
}
|
||||
47
doc/tutorial/3-iterate.md
Normal file
47
doc/tutorial/3-iterate.md
Normal file
@ -0,0 +1,47 @@
|
||||
## Iteration
|
||||
|
||||
Better Enums makes it easy to iterate over the values you have declared. For
|
||||
example, this:
|
||||
|
||||
#include <iostream>
|
||||
#include <enum.h>
|
||||
|
||||
<em>ENUM(Channel, int, Red, Green = 2, Blue)</em>
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
<em>for</em> (<em>size_t index = 0</em>; <em>index < Channel::_size</em>; <em>++index</em>) {
|
||||
Channel channel = <em>Channel::_values()[index]</em>;
|
||||
std::cout << channel._to_integral() << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
will print "0 2 3". And this:
|
||||
|
||||
<em>for</em> (<em>size_t index = 0</em>; <em>index < Channel::_size</em>; <em>++index</em>) {
|
||||
const char *name = <em>Channel::_names()[index]</em>;
|
||||
std::cout << name << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
will print "Red Green Blue".
|
||||
|
||||
---
|
||||
|
||||
If you are using $cxx11, you can have much nicer syntax:
|
||||
|
||||
~~~comment
|
||||
<em>for (Channel channel : Channel::_values())</em>
|
||||
std::cout << <em>channel._to_integral()</em> << " ";
|
||||
std::cout << std::endl;
|
||||
|
||||
<em>for (const char *name : Channel::_names())</em>
|
||||
std::cout << <em>name</em> << " ";
|
||||
std::cout << std::endl;
|
||||
~~~
|
||||
|
||||
---
|
||||
|
||||
return 0;
|
||||
}
|
||||
28
doc/tutorial/4-switch.md
Normal file
28
doc/tutorial/4-switch.md
Normal file
@ -0,0 +1,28 @@
|
||||
## Safe switch
|
||||
|
||||
A Better Enum can be used directly in a `switch` statement:
|
||||
|
||||
#include <iostream>
|
||||
<em>#include <enum.h></em>
|
||||
|
||||
<em>ENUM(Channel, int, Red, Green, Blue)</em>
|
||||
|
||||
int main()
|
||||
{
|
||||
Channel channel = Channel::Green;
|
||||
int n;
|
||||
|
||||
<em>switch </em>(<em>channel</em>) {
|
||||
<em>case Channel::Red</em>: n = 13; break;
|
||||
<em>case Channel::Green</em>: n = 37; break;
|
||||
<em>case Channel::Blue</em>: n = 42; break;
|
||||
}
|
||||
|
||||
If you miss a case or add a redundant one, your compiler should be able to give
|
||||
you a warning — try it!
|
||||
|
||||
---
|
||||
|
||||
std::cout << n << std::endl;
|
||||
return 0;
|
||||
}
|
||||
101
doc/tutorial/5-safety.md
Normal file
101
doc/tutorial/5-safety.md
Normal file
@ -0,0 +1,101 @@
|
||||
## Scope and safety
|
||||
|
||||
This tutorial shows some of the safety features of Better Enums: scope, how to
|
||||
control conversions, and the lack of a default constructor.
|
||||
|
||||
### Scope
|
||||
|
||||
You have probably noticed by now that Better Enums are scoped: when you declare
|
||||
|
||||
#include <cassert>
|
||||
<em>#include <enum.h></em>
|
||||
|
||||
<em>ENUM(Channel, int, Red = 1, Green, Blue)</em>
|
||||
|
||||
you don't get names such as `Red` in the global namespace. Instead, you get
|
||||
`Channel`, and `Red` is accessible as `Channel::Red`. This is no big deal in
|
||||
$cxx11, which has `enum class`. In $cxx98, however, this typically requires
|
||||
effort. Better Enums brings scope uniformly to both variants. So, despite the
|
||||
above declaration, you can safely declare
|
||||
|
||||
<em>ENUM(Node, char, Red, Black)</em>
|
||||
|
||||
and everything will work as expected.
|
||||
|
||||
int main()
|
||||
{
|
||||
assert((+<em>Channel::Red</em>)._to_integral() <em>!=</em> (+<em>Node::Red</em>)._to_integral());
|
||||
|
||||
### Implicit conversion
|
||||
|
||||
A major complaint in $cxx98 is that `enums` are implicitly convertible to
|
||||
integers. Unfortunately, that is also true of Better Enums, and I haven't found
|
||||
a way to forbid the conversions and still have switch case checking.
|
||||
|
||||
Better Enums can be made as safe as `enum class` in $cxx11, however. If your
|
||||
compiler supports `enum class` and you define
|
||||
`BETTER_ENUMS_STRICT_CONVERSION` before including `enum.h`, the following code
|
||||
will not compile:
|
||||
|
||||
~~~comment
|
||||
Channel channel = Channel::Red;
|
||||
int n = channel;
|
||||
~~~
|
||||
|
||||
The reason you have to opt into this feature with a macro is because it breaks
|
||||
compatibility with the $cxx98 version of Better Enums. Specifically, when
|
||||
writing a switch statement, you now have to do
|
||||
|
||||
~~~comment
|
||||
switch (channel) {
|
||||
case <em>+</em>Channel::Red: return 13;
|
||||
case <em>+</em>Channel::Green: return 37;
|
||||
case <em>+</em>Channel::Blue: return 42;
|
||||
}
|
||||
~~~
|
||||
|
||||
The difference is the explicit promotion with `+`. And, of course, if you had a
|
||||
bunch of code that relies on implicitly converting $cxx98 Better Enums to
|
||||
integers, it would break when switching to $cxx11 if strict conversions were the
|
||||
default.
|
||||
|
||||
### Default constructor
|
||||
|
||||
Better Enums don't have a default constructor, for three reasons.
|
||||
|
||||
- Better Enums is a library that can't know what your application would like
|
||||
the default value to be for each enum, or whether you even want one.
|
||||
- I chose not to leave the default value undefined, because the idea is to
|
||||
encourage the convention that whenever a Better Enum exists, it has a valid
|
||||
value. This is borrowed from typed functional programming.
|
||||
- Better Enums is still under development, and this option is the most
|
||||
future-proof.
|
||||
|
||||
So, if you uncomment this code, the file won't compile:
|
||||
|
||||
// Channel channel;
|
||||
|
||||
This may seem very strict, and I may relax it in the future. However, my guess
|
||||
is that there are few places where a default constructor is truly needed.
|
||||
|
||||
- If you want to opt in to a notion of default values, you can encode your
|
||||
project's policy into $cxx templates with ease, using building blocks Better
|
||||
Enums provides. The solution sketched
|
||||
[here](${prefix}demo/SpecialValues.html) is arguably more flexible than any
|
||||
fixed choice Better Enums could impose on you.
|
||||
- If a Better Enum value is the result of a large sequence of statements,
|
||||
you may be able to move those statements into a separate function that
|
||||
returns the value, and call it to initialize the Better Enum.
|
||||
- If you need to reserve memory for a Better Enum before it is created, you
|
||||
can do so by declaring a value of type `Enum::_integral`, as described in
|
||||
the [next tutorial](${prefix}tutorial/RepresentationAndAlignment.html).
|
||||
- I may add an ability to extend Better Enums, in which case you could add a
|
||||
default constructor on a per-type or global basis and have it do anything
|
||||
you want. I'd be glad to hear any feedback about your actual usage and
|
||||
needs.
|
||||
- Finally, Better Enums is under the BSD license so you can fork it and change
|
||||
it directly, though of course this can have some administration overhead.
|
||||
|
||||
---
|
||||
|
||||
}
|
||||
82
doc/tutorial/6-representation.md
Normal file
82
doc/tutorial/6-representation.md
Normal file
@ -0,0 +1,82 @@
|
||||
## Representation and alignment
|
||||
|
||||
Let's go over some of the low-level properties of a Better Enum. This time, we
|
||||
will declare a more unusual enum than the ones we have seen.
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
<em>#include <enum.h></em>
|
||||
|
||||
<em>ENUM(ContentType, short,
|
||||
CompressedVideo = 5, PCM = 8, Subtitles = 17, Comment = 44)</em>
|
||||
|
||||
This is for a hypothetical multimedia container file format. Perhaps the files
|
||||
have sections, and each one has a header:
|
||||
|
||||
<em>struct Header</em> {
|
||||
<em>ContentType type</em>;
|
||||
short flags;
|
||||
int offset;
|
||||
};
|
||||
|
||||
---
|
||||
|
||||
Here is what we have.
|
||||
|
||||
int main()
|
||||
{
|
||||
assert(<em>sizeof(ContentType) == 2</em>);
|
||||
|
||||
As you can see, `ContentType` behaves just like a `short`[^*], in fact it simply
|
||||
wraps one. This makes it possible to lay out structures in a predictable
|
||||
fashion:
|
||||
|
||||
Header header = {ContentType::PCM, 0, 0};
|
||||
|
||||
assert(<em>sizeof(header) == 8</em>);
|
||||
assert((size_t)&<em>header.flags -</em> (size_t)&<em>header.type == 2</em>);
|
||||
|
||||
---
|
||||
|
||||
`uint16_t` is called `ContentType`'s *underlying* or *representation* type. If
|
||||
you want to know the representation type of any enum you have declared, it is
|
||||
available as `::_integral`:
|
||||
|
||||
<em>ContentType::_integral</em> untrusted_value = 44;
|
||||
|
||||
Use this if you want a sized field to receive untrusted data, but aren't willing
|
||||
to call it `ContentType` yet because you have not validated it. Your validator
|
||||
will likely call `::_from_integral_nothrow`, perform any other validation your
|
||||
application requires, and then return `ContentType`.
|
||||
|
||||
ContentType type =
|
||||
ContentType::_from_integral(untrusted_value);
|
||||
std::cout << type._to_string() << std::endl;
|
||||
|
||||
---
|
||||
|
||||
You have probably noticed the initializers on each of the constants in
|
||||
`ContentType`. This allows you to declare sparse enums for compatibility with
|
||||
external protocols or previous versions of your software. The initializers don't
|
||||
need to be literal integers — they can be anything that the compiler would
|
||||
accept in a normal `enum` declaration. If there was a macro called
|
||||
`BIG_FAT_MACRO` declared above, we could have written
|
||||
`Subtitles = BIG_FAT_MACRO`. We could also have written
|
||||
`Subtitles = CompressedVideo`.
|
||||
|
||||
---
|
||||
|
||||
The in-memory representation of an enum value is simply the number it has been
|
||||
assigned by the compiler. You should be safe passing enums to functions like
|
||||
`fread` and `fwrite`, and casting memory blocks known to be safe to `struct`
|
||||
types containg enums. The enums will behave as expected.
|
||||
|
||||
---
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
[^*]: It should properly be a `uint16_t`, and the rest of the header fields
|
||||
should also be explicitly sized. However, this code is trying to be
|
||||
compatible with $cxx98, where those names aren't available in a portable
|
||||
manner.
|
||||
43
doc/tutorial/7-constexpr.md
Normal file
43
doc/tutorial/7-constexpr.md
Normal file
@ -0,0 +1,43 @@
|
||||
## Compile-time usage
|
||||
|
||||
When used with $cxx11, Better Enums are generated entirely during compilation.
|
||||
All the data is available for use by your own `constexpr` functions. The
|
||||
examples in *this* tutorial aren't very useful, but read the following tutorials
|
||||
to get an idea of what can be done. Here, you will see the basics.
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
#define BETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
#endif
|
||||
|
||||
<em>#include <enum.h></em>
|
||||
|
||||
<em>ENUM(Channel, int, Red = 1, Green = 2, Blue = 3)</em>
|
||||
|
||||
<em>constexpr</em> Channel channel = Channel::Green;
|
||||
<em>constexpr</em> int value = <em>channel._to_integral()</em>;
|
||||
|
||||
<em>constexpr</em> const char *name = <em>channel._to_string()</em>;
|
||||
<em>constexpr</em> Channel parsed = <em>Channel::_from_string("Red")</em>;
|
||||
|
||||
All of the above are computed during compilation. You can do apparently useless
|
||||
things such as:
|
||||
|
||||
<em>constexpr size_t length</em>(<em>const char *s</em>, <em>size_t index = 0</em>)
|
||||
{
|
||||
return <em>s[index] == '\0'</em> ? <em>index</em> : <em>length(s, index + 1)</em>;
|
||||
}
|
||||
|
||||
<em>constexpr</em> size_t <em>length_of_name_of_second_constant</em> =
|
||||
<em>length(Channel::_names()[1])</em>;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << <em>length_of_name_of_second_constant</em> << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Which prints "5", the length of "Green". That 5 was also computed during
|
||||
compilation.
|
||||
4
enum.h
4
enum.h
@ -1,6 +1,8 @@
|
||||
// This file is part of Better Enums, released under the BSD 2-clause license.
|
||||
// See LICENSE for details, or visit http://github.com/aantron/better-enums.
|
||||
|
||||
// Version 0.9.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef _BETTER_ENUMS_ENUM_H_
|
||||
@ -43,7 +45,7 @@
|
||||
# define _ENUM_NULLPTR nullptr
|
||||
#else
|
||||
# define _ENUM_CONSTEXPR
|
||||
# define _ENUM_NULLPTR ((void*)0)
|
||||
# define _ENUM_NULLPTR NULL
|
||||
#endif
|
||||
|
||||
#ifndef __GNUC__
|
||||
|
||||
@ -1,110 +0,0 @@
|
||||
// Basic conversions to/from strings and the underlying integral type.
|
||||
|
||||
#include <iostream>
|
||||
#include <enum.h>
|
||||
|
||||
ENUM(Channel, uint16_t, Red, Green = 2, Blue, Alias = Red)
|
||||
|
||||
// Enums should be treated like integers (in memory, Channel is a uint16_t), and
|
||||
// should generally be passed by value.
|
||||
void print_channel(Channel channel)
|
||||
{
|
||||
std::cout
|
||||
<< "channel \'"
|
||||
<< channel._to_string()
|
||||
<< "\' has value "
|
||||
<< channel._to_integral()
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
// A value must be assigned upon construction.
|
||||
Channel channel = Channel::Green;
|
||||
print_channel(channel);
|
||||
|
||||
// This will not work, though see example/6-traits.cc for an alternative.
|
||||
// Channel default_constructed_channel;
|
||||
|
||||
|
||||
|
||||
// Conversions from strings and the integral type. Static members of Channel
|
||||
// are prefixed with _ to avoid conflicts with constant names.
|
||||
// _from_integral is a checked cast.
|
||||
channel = Channel::_from_integral(0);
|
||||
print_channel(channel);
|
||||
|
||||
channel = Channel::_from_string("Blue");
|
||||
print_channel(channel);
|
||||
|
||||
channel = Channel::_from_string_nocase("bluE");
|
||||
print_channel(channel);
|
||||
|
||||
|
||||
|
||||
// Failed conversions.
|
||||
try {
|
||||
channel = Channel::_from_integral(15);
|
||||
throw std::logic_error("expected an exception");
|
||||
}
|
||||
catch (const std::runtime_error &e) { }
|
||||
|
||||
try {
|
||||
channel = Channel::_from_string("Purple");
|
||||
throw std::logic_error("expected an exception");
|
||||
}
|
||||
catch (const std::runtime_error &e) { }
|
||||
|
||||
try {
|
||||
channel = Channel::_from_string_nocase("bluee");
|
||||
throw std::logic_error("expected an exception");
|
||||
}
|
||||
catch (const std::runtime_error &e) { }
|
||||
|
||||
|
||||
|
||||
// Conversions with the nothrow (optional) interface.
|
||||
auto maybe_channel = Channel::_from_string_nothrow("foo");
|
||||
if (maybe_channel)
|
||||
throw std::logic_error("expected conversion failure");
|
||||
|
||||
maybe_channel = Channel::_from_string_nothrow("Blue");
|
||||
if (!maybe_channel)
|
||||
throw std::logic_error("expected successful conversion");
|
||||
print_channel(*maybe_channel);
|
||||
|
||||
|
||||
|
||||
// Unsafe unchecked cast.
|
||||
channel = Channel::_from_integral_unchecked(2);
|
||||
|
||||
|
||||
|
||||
// Direct operations on a constant require a promotion with the unary +
|
||||
// operator. This is an implementation artifact - constants are not actually
|
||||
// values of type Channel, but of type Channel::_Enumerated, and the
|
||||
// compiler isn't always able to implicitly promote the latter to the
|
||||
// former. + is used to force the promotion.
|
||||
std::cout << (+Channel::Green)._to_string() << std::endl;
|
||||
|
||||
// This will not work.
|
||||
// std::cout << (Channel::Green)._to_string() << std::endl;
|
||||
|
||||
|
||||
|
||||
// The type name is available as a string.
|
||||
std::cout << Channel::_name() << std::endl;
|
||||
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static_assert(sizeof(Channel) == sizeof(uint16_t),
|
||||
"enum has the same size as its underlying integral type");
|
||||
|
||||
static_assert(alignof(Channel) == alignof(uint16_t),
|
||||
"enum has the same alignment as its underlying integral type");
|
||||
|
||||
static_assert(std::is_same<Channel::_integral, uint16_t>(),
|
||||
"the underlying integral type is accessible as a member");
|
||||
23
example/1-hello-world.cc
Normal file
23
example/1-hello-world.cc
Normal file
@ -0,0 +1,23 @@
|
||||
// This file was generated automatically
|
||||
|
||||
// Hello, World!
|
||||
//
|
||||
// Download <a $download>enum.h</a>, then build this program with it:
|
||||
|
||||
#include <iostream>
|
||||
#include "enum.h"
|
||||
|
||||
ENUM(Word, int, Hello, World)
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << (+Word::Hello)._to_string() << ", "
|
||||
<< (+Word::World)._to_string() << "!"
|
||||
<< std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Run it, and you should see the output "Hello, World!"
|
||||
//
|
||||
// Congratulations, you have just created your first Better Enum!
|
||||
126
example/101-special-values.cc
Normal file
126
example/101-special-values.cc
Normal file
@ -0,0 +1,126 @@
|
||||
// This file was generated automatically
|
||||
|
||||
// Special values
|
||||
//
|
||||
// Suppose your project has a convention where each enum has special
|
||||
// <em>invalid</em> and <em>default</em> values. With Better Enums, you can
|
||||
// encode that directly at compile time, and then access each enum's special
|
||||
// values using syntax like Channel c = default_ and Channel c = invalid. This
|
||||
// can make your code adapt automatically to changes in enum definitions, as
|
||||
// well as make it easier to read and understand its intent.
|
||||
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <enum.h>
|
||||
|
||||
// Invalid
|
||||
//
|
||||
// Perhaps the invalid value is usually called Invalid, but not in all enums.
|
||||
// You can encode that using a function template for the common case, and a
|
||||
// macro that creates specializations:
|
||||
|
||||
template <typename Enum>
|
||||
constexpr Enum find_invalid() { return Enum::Invalid; }
|
||||
|
||||
#define OVERRIDE_INVALID(Enum, Value) \
|
||||
template<> \
|
||||
constexpr Enum find_invalid<Enum>() { return Enum::Value; }
|
||||
|
||||
// Now, you can declare enums like these:
|
||||
|
||||
ENUM(Channel, int, Red, Green, Blue, Invalid)
|
||||
|
||||
ENUM(Compression, int, Undefined, None, Huffman)
|
||||
OVERRIDE_INVALID(Compression, Undefined)
|
||||
|
||||
// and use them:
|
||||
|
||||
static_assert(find_invalid<Channel>() == +Channel::Invalid, "");
|
||||
static_assert(find_invalid<Compression>() == +Compression::Undefined, "");
|
||||
|
||||
// This even supports enums that don't have an invalid value at all. As long as
|
||||
// they don't have a constant called Invalid, you will get a compile-time error
|
||||
// if you try to call invalid() on them — as you probably should!
|
||||
//
|
||||
// Default
|
||||
//
|
||||
// To encode the policy on default values, we need to do a compile-time check
|
||||
// that the first value is not invalid. Otherwise, the technique is the same.
|
||||
|
||||
template <typename Enum>
|
||||
constexpr Enum find_default()
|
||||
{
|
||||
return
|
||||
Enum::_size < 2 ?
|
||||
throw std::logic_error("enum has no valid constants") :
|
||||
Enum::_values()[0] == find_invalid<Enum>() ?
|
||||
Enum::_values()[1] :
|
||||
Enum::_values()[0];
|
||||
}
|
||||
|
||||
#define OVERRIDE_DEFAULT(Enum, Value) \
|
||||
static_assert(Enum::Value != Enum::Invalid, \
|
||||
#Enum ": default cannot equal invalid"); \
|
||||
template<> \
|
||||
constexpr Enum find_default<Enum>() { return Enum::Value; }
|
||||
|
||||
// Usage:
|
||||
|
||||
static_assert(find_default<Channel>() == +Channel::Red, "");
|
||||
static_assert(find_default<Compression>() == +Compression::None, "");
|
||||
|
||||
// And, if you do
|
||||
|
||||
ENUM(Answer, int, Yes, No, Invalid)
|
||||
// OVERRIDE_DEFAULT(Answer, Invalid)
|
||||
|
||||
// you will get a helpful compile-time error saying Answer: default cannot equal
|
||||
// invalid.
|
||||
//
|
||||
// Making the syntax nicer
|
||||
//
|
||||
// For the final touch, we will make the syntax better by introducing new
|
||||
// "keywords" called default_ and invalid in such a way that we cause the
|
||||
// compiler to do type inference:
|
||||
|
||||
template <typename Enum>
|
||||
struct assert_enum {
|
||||
using check = typename Enum::_enumerated;
|
||||
using type = Enum;
|
||||
};
|
||||
|
||||
struct invalid_t {
|
||||
template <typename To>
|
||||
constexpr operator To() const { return find_invalid<To>(); }
|
||||
|
||||
template <typename To>
|
||||
constexpr To convert() const { return find_invalid<To>(); }
|
||||
};
|
||||
|
||||
struct default_t {
|
||||
template <typename To>
|
||||
constexpr operator To() const { return find_default<To>(); }
|
||||
};
|
||||
|
||||
constexpr invalid_t invalid{};
|
||||
constexpr default_t default_{};
|
||||
|
||||
static_assert(+Channel::Invalid == invalid, "");
|
||||
static_assert(+Compression::Undefined == invalid, "");
|
||||
|
||||
static_assert(+Channel::Red == default_, "");
|
||||
static_assert(+Compression::None == default_, "");
|
||||
|
||||
// We can now have nice code such as this:
|
||||
|
||||
int main()
|
||||
{
|
||||
Channel channel = default_;
|
||||
std::cout << channel._to_string() << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// There are many possible variations of these policies, but I think most of
|
||||
// them can be encoded in a reasonable fashion using the tools Better Enums
|
||||
// provides. Enjoy!
|
||||
49
example/102-bitset.cc
Normal file
49
example/102-bitset.cc
Normal file
@ -0,0 +1,49 @@
|
||||
// This file was generated automatically
|
||||
|
||||
// Bit sets
|
||||
//
|
||||
// If you want to use std::bitset or a similar library to use enums as keys into
|
||||
// a bit set, you need to know the number of bits at compile time. You can
|
||||
// easily automate this with Better Enums, even when constants are not declared
|
||||
// in increasing order.
|
||||
// We simply need to find the maximum value of any given enum type.
|
||||
|
||||
#include <bitset>
|
||||
#include <enum.h>
|
||||
|
||||
template <typename Enum>
|
||||
constexpr Enum max_loop(Enum accumulator, size_t index)
|
||||
{
|
||||
return
|
||||
index >= Enum::_size ? accumulator :
|
||||
Enum::_values()[index] > accumulator ?
|
||||
max_loop<Enum>(Enum::_values()[index], index + 1) :
|
||||
max_loop<Enum>(accumulator, index + 1);
|
||||
}
|
||||
|
||||
template <typename Enum>
|
||||
constexpr Enum max()
|
||||
{
|
||||
return max_loop<Enum>(Enum::_values()[0], 1);
|
||||
}
|
||||
|
||||
// And use that to declare a bit set template:
|
||||
|
||||
template <typename Enum>
|
||||
using EnumSet = std::bitset<max<Enum>()._to_integral() + 1>;
|
||||
|
||||
// Then rest is straightforward. The only issue is that, in $cxx11, it is
|
||||
// necessary to keep calling to_integral on the enums when passing them to
|
||||
// bitset functions. You may want to implement a more enum-friendly bit set
|
||||
// type, or overload unary operator -.
|
||||
|
||||
ENUM(Channel, int, Red, Green, Blue)
|
||||
ENUM(Depth, int, TrueColor = 1, HighColor = 0)
|
||||
|
||||
int main()
|
||||
{
|
||||
EnumSet<Channel> channels;
|
||||
EnumSet<Depth> depths;
|
||||
|
||||
return 0;
|
||||
}
|
||||
117
example/103-quine.cc
Normal file
117
example/103-quine.cc
Normal file
@ -0,0 +1,117 @@
|
||||
// This file was generated automatically
|
||||
|
||||
// Semi-quine
|
||||
//
|
||||
// Let's make a Better Enum compose its own definition. It won't be literally as
|
||||
// defined, since we will lose some information about initializers, but we will
|
||||
// be able to preserve their numeric values. We will reserve the memory buffers
|
||||
// at compile time.
|
||||
//
|
||||
// There are actually better ways to do this than shown here. You could define a
|
||||
// macro that expands to an ENUM declaration and also stringizes it. The point
|
||||
// here is to show some of the reflective capabilities of Better Enums, so you
|
||||
// can adapt them for cases where a macro is not sufficient.
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
|
||||
#ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
#define BETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
#endif
|
||||
|
||||
#include <enum.h>
|
||||
|
||||
#define HIGH_COLOR 0
|
||||
|
||||
ENUM(Channel, int, Red, Green, Blue)
|
||||
ENUM(Depth, int, TrueColor = 1, HighColor = HIGH_COLOR)
|
||||
|
||||
// First, we need to be able to get the length of each definition above. We will
|
||||
// assume that the underlying type is always int, and that the spacing
|
||||
// convention is followed as above. This allows us to write:
|
||||
|
||||
constexpr size_t value_length(int n, int bound = 10, size_t digits = 1)
|
||||
{
|
||||
return
|
||||
n < bound ? digits : value_length(n, bound * 10, digits + 1);
|
||||
}
|
||||
|
||||
constexpr size_t string_length(const char *s, size_t index = 0)
|
||||
{
|
||||
return s[index] == '\0' ? index : string_length(s, index + 1);
|
||||
}
|
||||
|
||||
template <typename Enum>
|
||||
constexpr size_t constants_length(size_t index = 0, size_t accumulator = 0)
|
||||
{
|
||||
return
|
||||
index >= Enum::_size ? accumulator :
|
||||
|
||||
constants_length<Enum>(
|
||||
index + 1, accumulator
|
||||
+ string_length(", ")
|
||||
+ string_length(Enum::_names()[index])
|
||||
+ string_length(" = ")
|
||||
+ value_length(
|
||||
Enum::_values()[index]._to_integral()));
|
||||
}
|
||||
|
||||
template <typename Enum>
|
||||
constexpr size_t declaration_length()
|
||||
{
|
||||
return
|
||||
string_length("ENUM(")
|
||||
+ string_length(Enum::_name())
|
||||
+ string_length(", int")
|
||||
+ constants_length<Enum>()
|
||||
+ string_length(")");
|
||||
}
|
||||
|
||||
// Now, we can declare:
|
||||
|
||||
char channel_definition[declaration_length<Channel>() + 1];
|
||||
char depth_definition[declaration_length<Depth>() + 1];
|
||||
|
||||
// And finally, the formatting function:
|
||||
|
||||
template <typename Enum>
|
||||
size_t format(char *buffer)
|
||||
{
|
||||
size_t offset = 0;
|
||||
|
||||
offset += std::sprintf(buffer, "ENUM(%s, int", Enum::_name());
|
||||
|
||||
for (Enum value : Enum::_values()) {
|
||||
offset +=
|
||||
std::sprintf(buffer + offset,
|
||||
", %s = %i",
|
||||
value._to_string(), value._to_integral());
|
||||
}
|
||||
|
||||
offset += std::sprintf(buffer + offset, ")");
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
size_t channel_length = format<Channel>(channel_definition);
|
||||
assert(channel_length + 1 == sizeof(channel_definition));
|
||||
|
||||
size_t depth_length = format<Depth>(depth_definition);
|
||||
assert(depth_length + 1 == sizeof(depth_definition));
|
||||
|
||||
std::cout << channel_definition << std::endl;
|
||||
std::cout << depth_definition << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This outputs:
|
||||
//
|
||||
// ENUM(Channel, int, Red = 0, Green = 1, Blue = 2)
|
||||
// ENUM(Depth, int, TrueColor = 1, HighColor = 0)
|
||||
//
|
||||
// This does have the advantage of not depending on anything else defined in the
|
||||
// program, which isn't as easy to achieve with stringization.
|
||||
142
example/2-conversions.cc
Normal file
142
example/2-conversions.cc
Normal file
@ -0,0 +1,142 @@
|
||||
// This file was generated automatically
|
||||
|
||||
// Conversions
|
||||
//
|
||||
// Let's begin by including enum.h and declaring our enum:
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
#include <enum.h>
|
||||
|
||||
ENUM(Channel, int, Cyan = 1, Magenta, Yellow, Black)
|
||||
|
||||
// We now have an int-sized enum with four constants.
|
||||
//
|
||||
// There are three groups of conversion functions: for strings, case-insensitive
|
||||
// strings, and integers. They all follow the same pattern, so I'll explain the
|
||||
// string functions in detail, and the rest can be understood by analogy.
|
||||
//
|
||||
// Strings
|
||||
//
|
||||
// There are three functions:
|
||||
//
|
||||
// 1. ._to_string
|
||||
// 2. ::_from_string
|
||||
// 3. ::_from_string_nothrow
|
||||
|
||||
int main()
|
||||
{
|
||||
Channel channel = Channel::Cyan;
|
||||
std::cout << channel._to_string() << " ";
|
||||
|
||||
// As you'd expect, the code above prints "Cyan".
|
||||
//
|
||||
// If channel is invalid — for example, if you simply cast the number "42"
|
||||
// to Channel — then the result of to_string is undefined.
|
||||
|
||||
channel = Channel::_from_string("Magenta");
|
||||
std::cout << channel._to_string() << " ";
|
||||
|
||||
// This is also straightforward. If you pass a string which is not the name of a
|
||||
// declared value, _from_string throws std::runtime_error.
|
||||
// If you don't want an exception, there is _from_string_nothrow:
|
||||
|
||||
better_enums::optional<Channel> maybe_channel =
|
||||
Channel::_from_string_nothrow("Yellow");
|
||||
|
||||
if (!maybe_channel)
|
||||
std::cout << "error";
|
||||
else
|
||||
std::cout << maybe_channel->_to_string() << " ";
|
||||
|
||||
// This returns an <em>optional value</em>, in the style of <a
|
||||
// href="http://www.boost.org/doc/libs/1_58_0/libs/optional/doc/html/index.html">boost::optional</a>
|
||||
// or the proposed <a
|
||||
// href="http://en.cppreference.com/w/cpp/experimental/optional">std::optional</a>.
|
||||
//
|
||||
// What that means for the above code is:
|
||||
//
|
||||
// 1. if the conversion succeeds, maybe_channel converts to true and
|
||||
// *maybe_channel is the converted value of type Channel,
|
||||
// 2. if the conversion fails, maybe_channel converts to false.
|
||||
//
|
||||
// In $cxx11, you can use auto to avoid writing out the optional type:
|
||||
//
|
||||
// auto maybe_channel = Channel::_from_string_nothrow("Yellow");
|
||||
// if (!maybe_channel)
|
||||
// std::cout << "error";
|
||||
// else
|
||||
// std::cout << maybe_channel->_to_string() << " ";
|
||||
//
|
||||
// Case-insensitive strings
|
||||
//
|
||||
// The "_nocase" string conversions follow the same pattern, except for the lack
|
||||
// of a "to_string_nocase".
|
||||
//
|
||||
// 1. ::_from_string_nocase
|
||||
// 2. ::_from_string_nocase_nothrow
|
||||
|
||||
channel = Channel::_from_string_nocase("cYaN");
|
||||
std::cout << channel._to_string() << " ";
|
||||
|
||||
maybe_channel = Channel::_from_string_nocase_nothrow("rEeD");
|
||||
assert(!maybe_channel);
|
||||
|
||||
// Integers
|
||||
//
|
||||
// And, it is similar with the <em>representation type</em> int:
|
||||
//
|
||||
// 1. ._to_integral
|
||||
// 2. ::_from_integral
|
||||
// 3. ::_from_integral_nothrow
|
||||
// 4. ::_from_integral_unchecked
|
||||
|
||||
channel = Channel::Cyan;
|
||||
std::cout << channel._to_integral() << " ";
|
||||
|
||||
channel = Channel::_from_integral(2);
|
||||
std::cout << channel._to_string() << " ";
|
||||
|
||||
maybe_channel = Channel::_from_integral_nothrow(0);
|
||||
assert(!maybe_channel);
|
||||
|
||||
// That prints "1 Magenta".
|
||||
//
|
||||
// _from_integral_unchecked is a no-op unchecked cast of integers to enums, so
|
||||
// use it carefully.
|
||||
|
||||
channel = Channel::_from_integral_unchecked(0);
|
||||
// Invalid - better not to try converting it to string!
|
||||
|
||||
// Aside
|
||||
//
|
||||
// You have certainly noticed that all the method names begin with underscores.
|
||||
// This is because they share scope with the enum constants that you declare.
|
||||
// Better Enums is trying to stay out of your way by using a prefix.
|
||||
//
|
||||
// Validity checking
|
||||
//
|
||||
// For completeness, Better Enums also provides three validity checking
|
||||
// functions, one for each of the groups of conversions — string,
|
||||
// case-insensitive string, and integer:
|
||||
|
||||
assert(Channel::_is_valid(3));
|
||||
assert(Channel::_is_valid("Magenta"));
|
||||
assert(Channel::_is_valid_nocase("cYaN"));
|
||||
|
||||
// Almost done.
|
||||
//
|
||||
// There is one unfortunate wrinkle. You cannot convert a literal constant such
|
||||
// as Channel::Cyan directly to, for example, a string. You have to prefix it
|
||||
// with +:
|
||||
|
||||
std::cout << (+Channel::Cyan)._to_string();
|
||||
|
||||
// This is due to some type gymnastics in the implementation of Better Enums.
|
||||
// The <a>Reference</a> section has a full explanation.
|
||||
// This concludes the first tutorial!
|
||||
|
||||
std::cout << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
// Iteration over all constants.
|
||||
|
||||
#include <iostream>
|
||||
#include <enum.h>
|
||||
|
||||
ENUM(Channel, int, Red = 3, Green = 4, Blue = 0)
|
||||
|
||||
int main()
|
||||
{
|
||||
// Listing declared values. Output is 3 4 0.
|
||||
for (Channel channel : Channel::_values())
|
||||
std::cout << channel._to_integral() << " ";
|
||||
std::cout << std::endl;
|
||||
|
||||
// Listing declared names. Output is Red Green Blue.
|
||||
for (const char *name : Channel::_names())
|
||||
std::cout << name << " ";
|
||||
std::cout << std::endl;
|
||||
|
||||
|
||||
|
||||
// Direct iterator usage. Output is Red.
|
||||
std::cout
|
||||
<< "first (using iterator): "
|
||||
<< *Channel::_names().begin()
|
||||
<< std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
42
example/3-iterate.cc
Normal file
42
example/3-iterate.cc
Normal file
@ -0,0 +1,42 @@
|
||||
// This file was generated automatically
|
||||
|
||||
// Iteration
|
||||
//
|
||||
// Better Enums makes it easy to iterate over the values you have declared. For
|
||||
// example, this:
|
||||
|
||||
#include <iostream>
|
||||
#include <enum.h>
|
||||
|
||||
ENUM(Channel, int, Red, Green = 2, Blue)
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
for (size_t index = 0; index < Channel::_size; ++index) {
|
||||
Channel channel = Channel::_values()[index];
|
||||
std::cout << channel._to_integral() << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
// will print "0 2 3". And this:
|
||||
|
||||
for (size_t index = 0; index < Channel::_size; ++index) {
|
||||
const char *name = Channel::_names()[index];
|
||||
std::cout << name << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
// will print "Red Green Blue".
|
||||
// If you are using $cxx11, you can have much nicer syntax:
|
||||
//
|
||||
// for (Channel channel : Channel::_values())
|
||||
// std::cout << channel._to_integral() << " ";
|
||||
// std::cout << std::endl;
|
||||
//
|
||||
// for (const char *name : Channel::_names())
|
||||
// std::cout << name << " ";
|
||||
// std::cout << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
// Switch case exhaustiveness checking.
|
||||
|
||||
#include <iostream>
|
||||
#include <enum.h>
|
||||
|
||||
ENUM(Channel, int, Red, Green, Blue)
|
||||
|
||||
void respond_to_channel(Channel channel)
|
||||
{
|
||||
// Try adding an extra case or removing one. Your compiler should issue a
|
||||
// warning.
|
||||
switch (channel) {
|
||||
case Channel::Red:
|
||||
std::cout << "red channel" << std::endl;
|
||||
break;
|
||||
|
||||
case Channel::Green:
|
||||
std::cout << "green channel" << std::endl;
|
||||
break;
|
||||
|
||||
case Channel::Blue:
|
||||
std::cout << "blue channel" << std::endl;
|
||||
break;
|
||||
|
||||
// A redundant case.
|
||||
// case 3:
|
||||
// break;
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
respond_to_channel(Channel::Red);
|
||||
respond_to_channel(Channel::Blue);
|
||||
respond_to_channel(Channel::Green);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,96 +0,0 @@
|
||||
// Usage in constexpr expressions. All members of an ENUM are constexpr when
|
||||
// given constant arguments. Iterators can be advanced at compile time by adding
|
||||
// 1 (note - this means using "+ 1", not "++". The "++" operator is not
|
||||
// constexpr).
|
||||
|
||||
// #define BETTER_ENUMS_FORCE_CONSTEXPR_TO_STRING
|
||||
|
||||
#include <iostream>
|
||||
#include <enum.h>
|
||||
|
||||
CONSTEXPR_TO_STRING_ENUM(Channel, int, Red, Green = 2, Blue)
|
||||
|
||||
// Initialization.
|
||||
constexpr Channel channel_1 = Channel::Green;
|
||||
|
||||
constexpr Channel channel_4 = Channel::_from_integral(2);
|
||||
|
||||
constexpr Channel channel_2 = Channel::_from_string("Blue");
|
||||
constexpr Channel channel_3 = Channel::_from_string_nocase("gReEn");
|
||||
|
||||
// Conversions to integers and strings.
|
||||
constexpr int channel_1_representation = channel_1._to_integral();
|
||||
constexpr const char *channel_1_name = channel_1._to_string();
|
||||
|
||||
// Validity checks (including against strings).
|
||||
constexpr bool should_be_valid_1 = Channel::_is_valid(2);
|
||||
constexpr bool should_be_invalid_1 = Channel::_is_valid(42);
|
||||
|
||||
constexpr bool should_be_valid_2 = Channel::_is_valid("Red");
|
||||
constexpr bool should_be_invalid_2 = Channel::_is_valid("red");
|
||||
|
||||
constexpr bool should_be_valid_3 = Channel::_is_valid_nocase("red");
|
||||
constexpr bool should_be_invalid_3 = Channel::_is_valid_nocase("reed");
|
||||
|
||||
// _names and _values collections and iterators.
|
||||
constexpr Channel channel_5 = *(Channel::_values().begin() + 1);
|
||||
constexpr const char *name_through_iterator =
|
||||
*(Channel::_names().begin() + 1);
|
||||
constexpr const char *name_through_subscript = Channel::_names()[2];
|
||||
|
||||
// Type name.
|
||||
constexpr auto name = Channel::_name();
|
||||
|
||||
// Explicit promotion.
|
||||
constexpr int converted = (+Channel::Green)._to_integral();
|
||||
|
||||
|
||||
|
||||
// The above, printed for verification.
|
||||
void print_channel(int number, Channel channel)
|
||||
{
|
||||
std::cout
|
||||
<< "channel_"
|
||||
<< number
|
||||
<< " is "
|
||||
<< channel._to_string()
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
#define PRINT(n) print_channel(n, channel_ ## n)
|
||||
|
||||
void print_validity(bool expected, bool actual)
|
||||
{
|
||||
std::cout
|
||||
<< "should be "
|
||||
<< expected
|
||||
<< ": "
|
||||
<< actual
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
PRINT(1);
|
||||
PRINT(2);
|
||||
PRINT(3);
|
||||
PRINT(4);
|
||||
|
||||
print_validity(true, should_be_valid_1);
|
||||
print_validity(false, should_be_invalid_1);
|
||||
print_validity(true, should_be_valid_2);
|
||||
print_validity(false, should_be_invalid_2);
|
||||
print_validity(true, should_be_valid_3);
|
||||
print_validity(false, should_be_invalid_3);
|
||||
|
||||
PRINT(5);
|
||||
|
||||
std::cout << "constexpr trimmed name: " << channel_1_name << std::endl;
|
||||
std::cout << "constexpr name through iterator: "
|
||||
<< name_through_iterator << std::endl;
|
||||
std::cout << "constexpr name through suscript: "
|
||||
<< name_through_subscript << std::endl;
|
||||
std::cout << "type name: " << name << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
28
example/4-switch.cc
Normal file
28
example/4-switch.cc
Normal file
@ -0,0 +1,28 @@
|
||||
// This file was generated automatically
|
||||
|
||||
// Safe switch
|
||||
//
|
||||
// A Better Enum can be used directly in a switch statement:
|
||||
|
||||
#include <iostream>
|
||||
#include <enum.h>
|
||||
|
||||
ENUM(Channel, int, Red, Green, Blue)
|
||||
|
||||
int main()
|
||||
{
|
||||
Channel channel = Channel::Green;
|
||||
int n;
|
||||
|
||||
switch (channel) {
|
||||
case Channel::Red: n = 13; break;
|
||||
case Channel::Green: n = 37; break;
|
||||
case Channel::Blue: n = 42; break;
|
||||
}
|
||||
|
||||
// If you miss a case or add a redundant one, your compiler should be able to
|
||||
// give you a warning — try it!
|
||||
|
||||
std::cout << n << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,64 +0,0 @@
|
||||
// Usage with STL containers.
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include <enum.h>
|
||||
|
||||
ENUM(Channel, int, Red, Green, Blue)
|
||||
|
||||
int main()
|
||||
{
|
||||
// Vectors of enums.
|
||||
std::vector<Channel> vector = {Channel::Red, Channel::Green};
|
||||
|
||||
vector.push_back(Channel::Red);
|
||||
vector.push_back(Channel::Blue);
|
||||
vector.push_back(Channel::Blue);
|
||||
vector.push_back(Channel::Red);
|
||||
|
||||
for (Channel channel : vector)
|
||||
std::cout << channel._to_string() << " ";
|
||||
std::cout << std::endl;
|
||||
|
||||
|
||||
|
||||
// Maps. Lack of a default constructor in the current version means that
|
||||
// std::map::operator[] usage is complicated. Insertion can still be done
|
||||
// with ::insert, and access with ::find.
|
||||
std::map<const char*, Channel> map = {{"first", Channel::Blue}};
|
||||
map.insert({"second", Channel::Green});
|
||||
|
||||
for (Channel channel : Channel::_values())
|
||||
map.insert({channel._to_string(), channel});
|
||||
|
||||
bool first = true;
|
||||
for (auto item : map) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
std::cout << ", ";
|
||||
|
||||
std::cout
|
||||
<< item.first
|
||||
<< " -> "
|
||||
<< item.second._to_string();
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
|
||||
|
||||
// Enums as map keys.
|
||||
std::map<Channel, const char*> descriptions =
|
||||
{{Channel::Red, "the red channel"},
|
||||
{Channel::Green, "the green channel"},
|
||||
{Channel::Blue, "the blue channel"}};
|
||||
|
||||
for (auto item : descriptions)
|
||||
std::cout << item.second << std::endl;
|
||||
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
101
example/5-safety.cc
Normal file
101
example/5-safety.cc
Normal file
@ -0,0 +1,101 @@
|
||||
// This file was generated automatically
|
||||
|
||||
// Scope and safety
|
||||
//
|
||||
// This tutorial shows some of the safety features of Better Enums: scope, how
|
||||
// to control conversions, and the lack of a default constructor.
|
||||
//
|
||||
// Scope
|
||||
//
|
||||
// You have probably noticed by now that Better Enums are scoped: when you
|
||||
// declare
|
||||
|
||||
#include <cassert>
|
||||
#include <enum.h>
|
||||
|
||||
ENUM(Channel, int, Red = 1, Green, Blue)
|
||||
|
||||
// you don't get names such as Red in the global namespace. Instead, you get
|
||||
// Channel, and Red is accessible as Channel::Red. This is no big deal in
|
||||
// $cxx11, which has enum class. In $cxx98, however, this typically requires
|
||||
// effort. Better Enums brings scope uniformly to both variants. So, despite the
|
||||
// above declaration, you can safely declare
|
||||
|
||||
ENUM(Node, char, Red, Black)
|
||||
|
||||
// and everything will work as expected.
|
||||
|
||||
int main()
|
||||
{
|
||||
assert((+Channel::Red)._to_integral() != (+Node::Red)._to_integral());
|
||||
|
||||
// Implicit conversion
|
||||
//
|
||||
// A major complaint in $cxx98 is that enums are implicitly convertible to
|
||||
// integers. Unfortunately, that is also true of Better Enums, and I haven't
|
||||
// found a way to forbid the conversions and still have switch case checking.
|
||||
//
|
||||
// Better Enums can be made as safe as enum class in $cxx11, however. If your
|
||||
// compiler supports enum class and you define BETTER_ENUMS_STRICT_CONVERSION
|
||||
// before including enum.h, the following code will not compile:
|
||||
//
|
||||
// Channel channel = Channel::Red;
|
||||
// int n = channel;
|
||||
//
|
||||
// The reason you have to opt into this feature with a macro is because it
|
||||
// breaks compatibility with the $cxx98 version of Better Enums. Specifically,
|
||||
// when writing a switch statement, you now have to do
|
||||
//
|
||||
// switch (channel) {
|
||||
// case +Channel::Red: return 13;
|
||||
// case +Channel::Green: return 37;
|
||||
// case +Channel::Blue: return 42;
|
||||
// }
|
||||
//
|
||||
// The difference is the explicit promotion with +. And, of course, if you had a
|
||||
// bunch of code that relies on implicitly converting $cxx98 Better Enums to
|
||||
// integers, it would break when switching to $cxx11 if strict conversions were
|
||||
// the default.
|
||||
//
|
||||
// Default constructor
|
||||
//
|
||||
// Better Enums don't have a default constructor, for three reasons.
|
||||
//
|
||||
// 1. Better Enums is a library that can't know what your application would
|
||||
// like the default value to be for each enum, or whether you even want
|
||||
// one.
|
||||
// 2. I chose not to leave the default value undefined, because the idea is to
|
||||
// encourage the convention that whenever a Better Enum exists, it has a
|
||||
// valid value. This is borrowed from typed functional programming.
|
||||
// 3. Better Enums is still under development, and this option is the most
|
||||
// future-proof.
|
||||
//
|
||||
// So, if you uncomment this code, the file won't compile:
|
||||
|
||||
// Channel channel;
|
||||
|
||||
// This may seem very strict, and I may relax it in the future. However, my
|
||||
// guess is that there are few places where a default constructor is truly
|
||||
// needed.
|
||||
//
|
||||
// 1. If you want to opt in to a notion of default values, you can encode your
|
||||
// project's policy into $cxx templates with ease, using building blocks
|
||||
// Better Enums provides. The solution sketched <a
|
||||
// href="${prefix}demo/SpecialValues.html">here</a> is arguably more
|
||||
// flexible than any fixed choice Better Enums could impose on you.
|
||||
// 2. If a Better Enum value is the result of a large sequence of statements,
|
||||
// you may be able to move those statements into a separate function that
|
||||
// returns the value, and call it to initialize the Better Enum.
|
||||
// 3. If you need to reserve memory for a Better Enum before it is created,
|
||||
// you can do so by declaring a value of type Enum::_integral, as described
|
||||
// in the <a href="${prefix}tutorial/RepresentationAndAlignment.html">next
|
||||
// tutorial</a>.
|
||||
// 4. I may add an ability to extend Better Enums, in which case you could add
|
||||
// a default constructor on a per-type or global basis and have it do
|
||||
// anything you want. I'd be glad to hear any feedback about your actual
|
||||
// usage and needs.
|
||||
// 5. Finally, Better Enums is under the BSD license so you can fork it and
|
||||
// change it directly, though of course this can have some administration
|
||||
// overhead.
|
||||
|
||||
}
|
||||
67
example/6-representation.cc
Normal file
67
example/6-representation.cc
Normal file
@ -0,0 +1,67 @@
|
||||
// This file was generated automatically
|
||||
|
||||
// Representation and alignment
|
||||
//
|
||||
// Let's go over some of the low-level properties of a Better Enum. This time,
|
||||
// we will declare a more unusual enum than the ones we have seen.
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <enum.h>
|
||||
|
||||
ENUM(ContentType, short,
|
||||
CompressedVideo = 5, PCM = 8, Subtitles = 17, Comment = 44)
|
||||
|
||||
// This is for a hypothetical multimedia container file format. Perhaps the
|
||||
// files have sections, and each one has a header:
|
||||
|
||||
struct Header {
|
||||
ContentType type;
|
||||
short flags;
|
||||
int offset;
|
||||
};
|
||||
|
||||
// Here is what we have.
|
||||
|
||||
int main()
|
||||
{
|
||||
assert(sizeof(ContentType) == 2);
|
||||
|
||||
// As you can see, ContentType behaves just like a short, in fact it simply
|
||||
// wraps one. This makes it possible to lay out structures in a predictable
|
||||
// fashion:
|
||||
|
||||
Header header = {ContentType::PCM, 0, 0};
|
||||
|
||||
assert(sizeof(header) == 8);
|
||||
assert((size_t)&header.flags - (size_t)&header.type == 2);
|
||||
|
||||
// uint16_t is called ContentType's <em>underlying</em> or
|
||||
// <em>representation</em> type. If you want to know the representation type of
|
||||
// any enum you have declared, it is available as ::_integral:
|
||||
|
||||
ContentType::_integral untrusted_value = 44;
|
||||
|
||||
// Use this if you want a sized field to receive untrusted data, but aren't
|
||||
// willing to call it ContentType yet because you have not validated it. Your
|
||||
// validator will likely call ::_from_integral_nothrow, perform any other
|
||||
// validation your application requires, and then return ContentType.
|
||||
|
||||
ContentType type =
|
||||
ContentType::_from_integral(untrusted_value);
|
||||
std::cout << type._to_string() << std::endl;
|
||||
|
||||
// You have probably noticed the initializers on each of the constants in
|
||||
// ContentType. This allows you to declare sparse enums for compatibility with
|
||||
// external protocols or previous versions of your software. The initializers
|
||||
// don't need to be literal integers — they can be anything that the
|
||||
// compiler would accept in a normal enum declaration. If there was a macro
|
||||
// called BIG_FAT_MACRO declared above, we could have written Subtitles =
|
||||
// BIG_FAT_MACRO. We could also have written Subtitles = CompressedVideo.
|
||||
// The in-memory representation of an enum value is simply the number it has
|
||||
// been assigned by the compiler. You should be safe passing enums to functions
|
||||
// like fread and fwrite, and casting memory blocks known to be safe to struct
|
||||
// types containg enums. The enums will behave as expected.
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
// Using traits to capture project conventions on enums.
|
||||
|
||||
// In this example, a project wants to have a notion of "default value" for all
|
||||
// enums. Better Enums doesn't provide this, but it can be added easily with a
|
||||
// traits class, as shown here.
|
||||
|
||||
#include <iostream>
|
||||
#include <enum.h>
|
||||
|
||||
|
||||
|
||||
// Adopt the convention that the first value in an enum is the default value.
|
||||
template <typename Enum>
|
||||
constexpr const Enum default_()
|
||||
{
|
||||
return Enum::_values()[0];
|
||||
}
|
||||
|
||||
// Make it possible to override the convention for specific enums.
|
||||
#define ENUM_DEFAULT(Enum, Default) \
|
||||
template <> \
|
||||
constexpr const Enum default_<Enum>() \
|
||||
{ \
|
||||
return Enum::Default; \
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Default will be Red, because it is first.
|
||||
ENUM(Channel, int, Red, Green, Blue)
|
||||
|
||||
// Default will be TrueColor, even though it is not first.
|
||||
ENUM(Depth, int, HighColor, TrueColor)
|
||||
ENUM_DEFAULT(Depth, TrueColor)
|
||||
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
// Default construction can now be simulated for some purposes, and the
|
||||
// default value is still declared in one place, not all over the program
|
||||
// code.
|
||||
Depth depth = default_<Depth>();
|
||||
std::cout << depth._to_string() << std::endl;
|
||||
|
||||
std::cout << default_<Channel>()._to_string() << std::endl;
|
||||
std::cout << default_<Depth>()._to_string() << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Also works at compile-time.
|
||||
constexpr auto value = default_<Channel>();
|
||||
@ -1,44 +0,0 @@
|
||||
// Usage with std::bitset.
|
||||
|
||||
#include <bitset>
|
||||
#include <iostream>
|
||||
#include <enum.h>
|
||||
|
||||
// Computes the maximum value of an enum at compile time.
|
||||
template <typename Enum>
|
||||
constexpr Enum maximum(Enum accumulator = Enum::_values()[0], size_t index = 1)
|
||||
{
|
||||
return
|
||||
index >= Enum::_size ? accumulator :
|
||||
Enum::_values()[index] > accumulator ?
|
||||
maximum(Enum::_values()[index], index + 1) :
|
||||
maximum(accumulator, index + 1);
|
||||
}
|
||||
|
||||
ENUM(Channel, int, Red, Green, Blue)
|
||||
|
||||
int main()
|
||||
{
|
||||
using ChannelSet = std::bitset<maximum<Channel>()._to_integral() + 1>;
|
||||
|
||||
ChannelSet red_only;
|
||||
red_only.set(Channel::Red);
|
||||
|
||||
ChannelSet blue_only;
|
||||
blue_only.set(Channel::Blue);
|
||||
|
||||
ChannelSet red_and_blue = red_only | blue_only;
|
||||
|
||||
for (Channel channel : Channel::_values()) {
|
||||
std::cout
|
||||
<< channel._to_string()
|
||||
<< " bit is set to "
|
||||
<< red_and_blue[channel._to_integral()]
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
if (red_and_blue[Channel::Green])
|
||||
std::cout << "bit set contains Green" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
46
example/7-constexpr.cc
Normal file
46
example/7-constexpr.cc
Normal file
@ -0,0 +1,46 @@
|
||||
// This file was generated automatically
|
||||
|
||||
// Compile-time usage
|
||||
//
|
||||
// When used with $cxx11, Better Enums are generated entirely during
|
||||
// compilation. All the data is available for use by your own constexpr
|
||||
// functions. The examples in <em>this</em> tutorial aren't very useful, but
|
||||
// read the following tutorials to get an idea of what can be done. Here, you
|
||||
// will see the basics.
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
#define BETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
#endif
|
||||
|
||||
#include <enum.h>
|
||||
|
||||
ENUM(Channel, int, Red = 1, Green = 2, Blue = 3)
|
||||
|
||||
constexpr Channel channel = Channel::Green;
|
||||
constexpr int value = channel._to_integral();
|
||||
|
||||
constexpr const char *name = channel._to_string();
|
||||
constexpr Channel parsed = Channel::_from_string("Red");
|
||||
|
||||
// All of the above are computed during compilation. You can do apparently
|
||||
// useless things such as:
|
||||
|
||||
constexpr size_t length(const char *s, size_t index = 0)
|
||||
{
|
||||
return s[index] == '\0' ? index : length(s, index + 1);
|
||||
}
|
||||
|
||||
constexpr size_t length_of_name_of_second_constant =
|
||||
length(Channel::_names()[1]);
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << length_of_name_of_second_constant << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Which prints "5", the length of "Green". That 5 was also computed during
|
||||
// compilation.
|
||||
@ -1,81 +0,0 @@
|
||||
// Compile-time iteration. This example generates an approximation of enum
|
||||
// declarations (without explicit "="" settings) at run time. The storage space
|
||||
// for this is reserved at compile time, however.
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <enum.h>
|
||||
|
||||
CONSTEXPR_TO_STRING_ENUM(Channel, int, Red, Green, Blue)
|
||||
CONSTEXPR_TO_STRING_ENUM(Depth, int, TrueColor, HighColor)
|
||||
|
||||
// Computes the length of a string.
|
||||
constexpr size_t string_length(const char *s, size_t index = 0)
|
||||
{
|
||||
return s[index] == '\0' ? index : string_length(s, index + 1);
|
||||
}
|
||||
|
||||
// Runs over all the constants in an enum and adds up the lengths of their
|
||||
// names.
|
||||
template <typename Enum>
|
||||
constexpr size_t total_names_length(size_t accumulator = 0, size_t index = 0)
|
||||
{
|
||||
return
|
||||
index == Enum::_size ? accumulator :
|
||||
total_names_length<Enum>
|
||||
(accumulator + string_length(Enum::_names()[index]), index + 1);
|
||||
}
|
||||
|
||||
// Computes the total length of an ENUM declaration, assuming the type is int.
|
||||
// The summands correspond to each of the tokens and spaces (i.e., "ENUM", "(",
|
||||
// etc.). (Enum::_size - 1) * 2 is added to account for each comma and space
|
||||
// following each constant name, except for the last one. The final 1 is added
|
||||
// to account for the null terminator.
|
||||
template <typename Enum>
|
||||
constexpr size_t declaration_length()
|
||||
{
|
||||
return
|
||||
4 + 1 + string_length(Enum::_name()) + 1 + 1 + 3 + 1 + 1 +
|
||||
total_names_length<Enum>() + (Enum::_size - 1) * 2 + 1 + 1 + 1;
|
||||
}
|
||||
|
||||
// Formats the declaration into space already reserved.
|
||||
template <typename Enum>
|
||||
void format_declaration(char *storage)
|
||||
{
|
||||
std::strcat(storage, "ENUM(");
|
||||
std::strcat(storage, Enum::_name());
|
||||
std::strcat(storage, ", int, ");
|
||||
|
||||
for (auto name_iterator = Enum::_names().begin();
|
||||
name_iterator < Enum::_names().end() - 1; ++name_iterator) {
|
||||
|
||||
std::strcat(storage, *name_iterator);
|
||||
std::strcat(storage, ", ");
|
||||
}
|
||||
std::strcat(storage, Enum::_names()[Enum::_size - 1]);
|
||||
|
||||
std::strcat(storage, ");");
|
||||
|
||||
assert(std::strlen(storage) == declaration_length<Enum>() - 1);
|
||||
}
|
||||
|
||||
// Reserve space for the formatted declaration of each enum. These buffers
|
||||
// should be zeroed at load time or during code generation, so, semantically,
|
||||
// they contain the empty string.
|
||||
char channel_declaration[declaration_length<Channel>()];
|
||||
char depth_declaration[declaration_length<Depth>()];
|
||||
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
format_declaration<Channel>(channel_declaration);
|
||||
std::cout << channel_declaration << std::endl;
|
||||
|
||||
format_declaration<Depth>(depth_declaration);
|
||||
std::cout << depth_declaration << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -26,8 +26,8 @@
|
||||
# somewhere in your include path.
|
||||
# 1. Run python make_macros.py 512 128 > MACRO_FILE
|
||||
# 2. Build your code with an additional compiler flag:
|
||||
# - for gcc and clang, -BETTER_ENUMS_MACRO_FILE='<MACRO_FILE>'
|
||||
# - for VC++, /BETTER_ENUMS_MACRO_FILE='<MACRO_FILE>'
|
||||
# - for gcc and clang, -DBETTER_ENUMS_MACRO_FILE='<MACRO_FILE>'
|
||||
# - for VC++, /DBETTER_ENUMS_MACRO_FILE='<MACRO_FILE>'
|
||||
# or use any other method of getting these macros declared.
|
||||
# 3. Compile your code. Your macro file should be included, and enum.h should
|
||||
# happily work with whatever limits you chose.
|
||||
|
||||
@ -1,45 +1,12 @@
|
||||
ifndef CXX
|
||||
CXX := c++
|
||||
endif
|
||||
.PHONY : platform
|
||||
platform :
|
||||
make -C ../doc examples
|
||||
python test.py
|
||||
|
||||
ifndef CXXFLAGS
|
||||
CXXFLAGS := -std=c++11 -Wall -I .. -o
|
||||
endif
|
||||
|
||||
CXXTEST_H := cxxtest/tests.h
|
||||
CXXTEST_SRC := $(CXXTEST_H:.h=.cc)
|
||||
CXXTEST_BIN := $(CXXTEST_H:.h=.exe)
|
||||
|
||||
LINK_BIN := link/link.exe
|
||||
|
||||
PERFORMANCE_SRC := $(wildcard performance/*.cc)
|
||||
PERFORMANCE_BIN := $(PERFORMANCE_SRC:.cc=.exe)
|
||||
.PHONY : clean
|
||||
clean :
|
||||
rm -rf platform
|
||||
|
||||
.PHONY : default
|
||||
default : run
|
||||
@:
|
||||
|
||||
$(CXXTEST_SRC) : $(CXXTEST_H) Makefile
|
||||
cxxtestgen --error-printer -o $@ $<
|
||||
|
||||
$(CXXTEST_BIN) : $(CXXTEST_SRC) ../*.h Makefile
|
||||
$(CXX) $(CXXFLAGS) $@ $<
|
||||
@echo Passed `grep 'static_assert_1' $(CXXTEST_H) | wc -l` static assertions
|
||||
|
||||
$(LINK_BIN) : link/*.h link/*.cc ../*.h Makefile
|
||||
$(CXX) $(CXXFLAGS) $@ link/*.cc
|
||||
|
||||
$(PERFORMANCE_BIN) : %.exe : %.cc
|
||||
@echo $<
|
||||
@/usr/bin/time -p $(CXX) $(CXXFLAGS) $@ $<
|
||||
@rm $@
|
||||
|
||||
.PHONY : run
|
||||
run : $(CXXTEST_BIN) $(LINK_BIN) $(PERFORMANCE_BIN)
|
||||
./$(CXXTEST_BIN)
|
||||
./$(LINK_BIN)
|
||||
|
||||
.PHONY : clean
|
||||
clean :
|
||||
rm -f $(CXXTEST_SRC)
|
||||
find -E . -regex '.*\.exe' | xargs rm -f
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
#define static_assert_1(e) static_assert(e, #e)
|
||||
|
||||
#ifdef BETTER_ENUMS_FORCE_STRICT_CONVERSION
|
||||
#ifdef BETTER_ENUMS_STRICT_CONVERSION
|
||||
# define STRICT 1
|
||||
#else
|
||||
# define STRICT 0
|
||||
@ -51,7 +51,6 @@ static_assert_1((std::is_same<decltype(Channel::Red), Channel::_enumerated>()));
|
||||
|
||||
|
||||
// Supported constructors.
|
||||
static_assert_1(!std::is_default_constructible<Channel>());
|
||||
|
||||
#ifdef __clang__
|
||||
static_assert_1(std::is_trivially_copyable<Channel>());
|
||||
@ -61,6 +60,9 @@ static_assert_1((std::is_constructible<Channel, Channel::_enumerated>()));
|
||||
static_assert_1(!(std::is_constructible<Channel, Channel::_integral>()));
|
||||
static_assert_1(!(std::is_constructible<Channel, Depth>()));
|
||||
|
||||
// Commented out temporarily due to GCC 4.7- bug.
|
||||
// static_assert_1(!std::is_default_constructible<Channel>());
|
||||
|
||||
|
||||
|
||||
// Intended implicit conversions.
|
||||
@ -110,7 +112,7 @@ static_assert_1(*Channel::_values().begin() == +Channel::Red);
|
||||
static_assert_1(*(Channel::_values().end() - 1) == +Channel::Blue);
|
||||
static_assert_1(Channel::_values()[1] == +Channel::Green);
|
||||
|
||||
#ifdef BETTER_ENUMS_FORCE_CONSTEXPR_TO_STRING
|
||||
#ifdef BETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
|
||||
constexpr bool same_string(const char *r, const char *s, size_t index = 0)
|
||||
{
|
||||
@ -127,7 +129,7 @@ static_assert_1(same_string(*Depth::_names().begin(), "HighColor"));
|
||||
static_assert_1(same_string(*(Depth::_names().end() - 1), "TrueColor"));
|
||||
static_assert_1(same_string(Depth::_names()[0], "HighColor"));
|
||||
|
||||
#endif // #ifdef BETTER_ENUMS_FORCE_CONSTEXPR_TO_STRING
|
||||
#endif // #ifdef BETTER_ENUMS_CONSTEXPR_TO_STRING
|
||||
|
||||
#endif // #ifdef _ENUM_HAVE_CONSTEXPR
|
||||
|
||||
|
||||
1
test/expect/1-hello-world
Normal file
1
test/expect/1-hello-world
Normal file
@ -0,0 +1 @@
|
||||
Hello, World!
|
||||
1
test/expect/101-special-values
Normal file
1
test/expect/101-special-values
Normal file
@ -0,0 +1 @@
|
||||
Red
|
||||
0
test/expect/102-bitset
Normal file
0
test/expect/102-bitset
Normal file
2
test/expect/103-quine
Normal file
2
test/expect/103-quine
Normal file
@ -0,0 +1,2 @@
|
||||
ENUM(Channel, int, Red = 0, Green = 1, Blue = 2)
|
||||
ENUM(Depth, int, TrueColor = 1, HighColor = 0)
|
||||
1
test/expect/2-conversions
Normal file
1
test/expect/2-conversions
Normal file
@ -0,0 +1 @@
|
||||
Cyan Magenta Yellow Cyan 1 Magenta Cyan
|
||||
2
test/expect/3-iterate
Normal file
2
test/expect/3-iterate
Normal file
@ -0,0 +1,2 @@
|
||||
0 2 3
|
||||
Red Green Blue
|
||||
1
test/expect/4-switch
Normal file
1
test/expect/4-switch
Normal file
@ -0,0 +1 @@
|
||||
37
|
||||
0
test/expect/5-safety
Normal file
0
test/expect/5-safety
Normal file
1
test/expect/6-representation
Normal file
1
test/expect/6-representation
Normal file
@ -0,0 +1 @@
|
||||
Comment
|
||||
1
test/expect/7-constexpr
Normal file
1
test/expect/7-constexpr
Normal file
@ -0,0 +1 @@
|
||||
5
|
||||
228
test/test.py
Executable file
228
test/test.py
Executable file
@ -0,0 +1,228 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
|
||||
BASE_DIRECTORY = "platform"
|
||||
CXXTEST_SOURCE = "cxxtest/tests.h"
|
||||
CXXTEST_GENERATED = "cxxtest/tests.cc"
|
||||
|
||||
quiet = True
|
||||
|
||||
|
||||
|
||||
def file_title(path):
|
||||
return os.path.splitext(os.path.basename(path))[0]
|
||||
|
||||
|
||||
|
||||
expected_example_outputs = {}
|
||||
|
||||
def load_expected_outputs():
|
||||
files = glob.glob("expect/*")
|
||||
for file in files:
|
||||
stream = open(file)
|
||||
try:
|
||||
content = stream.read()
|
||||
finally:
|
||||
stream.close()
|
||||
|
||||
expected_example_outputs[file_title(file)] = content
|
||||
|
||||
|
||||
|
||||
def run(command, catch_warnings = False):
|
||||
if not quiet:
|
||||
print command
|
||||
|
||||
try:
|
||||
output = subprocess.check_output(command.split(),
|
||||
stderr = subprocess.STDOUT)
|
||||
|
||||
if not catch_warnings:
|
||||
return output
|
||||
else:
|
||||
if re.search("warning", output) != None:
|
||||
raise subprocess.CalledProcessError(0, command, output)
|
||||
return output
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print e.output
|
||||
print command, "failed"
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
class Configuration(object):
|
||||
def __init__(self, subdirectory, command, skip_examples = []):
|
||||
self._subdirectory = subdirectory
|
||||
self._command = command
|
||||
self._skip_examples = skip_examples
|
||||
|
||||
def elaborate(self, include, output, source):
|
||||
command = self._command
|
||||
if command.startswith("clang") or command.startswith("g++"):
|
||||
return "%s -I%s -Wall -o %s %s" % (command, include, output, source)
|
||||
else:
|
||||
raise Error("unrecognized compiler in '%s'" % command)
|
||||
|
||||
def make_directories(self):
|
||||
base = self.directory()
|
||||
directories = \
|
||||
[base,
|
||||
os.path.join(base, "example"),
|
||||
os.path.join(base, "cxxtest"),
|
||||
os.path.join(base, "link"),
|
||||
os.path.join(base, "performance")]
|
||||
|
||||
for directory in directories:
|
||||
if not os.path.lexists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
def make_all(self):
|
||||
print self._command
|
||||
|
||||
self.make_directories()
|
||||
|
||||
base = self.directory()
|
||||
|
||||
examples = glob.glob("../example/*.cc")
|
||||
example_directory = os.path.join(base, "example")
|
||||
for file in examples:
|
||||
title = file_title(file)
|
||||
|
||||
if title in self._skip_examples:
|
||||
continue
|
||||
|
||||
if title not in expected_example_outputs:
|
||||
print "no expected output for example", title
|
||||
sys.exit(1)
|
||||
|
||||
binary = os.path.join(example_directory, title) + ".exe"
|
||||
|
||||
command = self.elaborate("..", binary, file)
|
||||
run(command, True)
|
||||
|
||||
output = run(binary)
|
||||
if output != expected_example_outputs[title]:
|
||||
print output
|
||||
print "output does not match expected output"
|
||||
sys.exit(1)
|
||||
|
||||
cxxtest_binary = os.path.join(base, "cxxtest", "tests.exe")
|
||||
command = self.elaborate("..", cxxtest_binary, CXXTEST_GENERATED)
|
||||
run(command, True)
|
||||
run(cxxtest_binary)
|
||||
|
||||
link_sources = glob.glob("link/*.cc")
|
||||
link_binary = os.path.join(base, "link", "link.exe")
|
||||
command = self.elaborate("..", link_binary, " ".join(link_sources))
|
||||
run(command, True)
|
||||
|
||||
performance_sources = glob.glob("performance/*.cc")
|
||||
performance_directory = os.path.join(base, "performance")
|
||||
for file in performance_sources:
|
||||
title = file_title(file)
|
||||
|
||||
binary = os.path.join(performance_directory, title) + ".exe"
|
||||
|
||||
command = "time " + self.elaborate("..", binary, file)
|
||||
output = run(command, True)
|
||||
|
||||
output_file = os.path.join(performance_directory, title) + ".times"
|
||||
stream = open(output_file, "w")
|
||||
try:
|
||||
stream.write(output)
|
||||
finally:
|
||||
stream.close()
|
||||
|
||||
def directory(self):
|
||||
return os.path.join(BASE_DIRECTORY, self._subdirectory)
|
||||
|
||||
|
||||
|
||||
skip_cxx98 = ["101-special-values", "102-bitset", "103-quine", "7-constexpr"]
|
||||
skip_strict = ["4-switch"]
|
||||
|
||||
def modern_gnu(command):
|
||||
return [
|
||||
Configuration(command + "-c++11", command + " -std=c++11"),
|
||||
Configuration(command + "-strict-conversion",
|
||||
command + " -std=c++11 " +
|
||||
"-DBETTER_ENUMS_STRICT_CONVERSION", skip_strict),
|
||||
Configuration(command + "-all-constexpr",
|
||||
command + " -std=c++11 " +
|
||||
"-DBETTER_ENUMS_CONSTEXPR_TO_STRING"),
|
||||
Configuration(command + "-c++98", command + " -std=c++98", skip_cxx98)
|
||||
]
|
||||
|
||||
# g++46 should be one of these, but g++46 c++11 mode currently not supported due
|
||||
# to the presence of this bug and lack of a workaround in enum.h.
|
||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54086
|
||||
def older_gnu(command):
|
||||
return [
|
||||
Configuration(command + "-c++0x", command + " -std=c++0x"),
|
||||
Configuration(command + "-strict-conversion",
|
||||
command + " -std=c++0x " +
|
||||
"-DBETTER_ENUMS_STRICT_CONVERSION", skip_strict),
|
||||
Configuration(command + "-all-constexpr",
|
||||
command + " -std=c++0x " +
|
||||
"-DBETTER_ENUMS_CONSTEXPR_TO_STRING"),
|
||||
Configuration(command + "-c++98", command + " -std=c++98", skip_cxx98)
|
||||
]
|
||||
|
||||
def gnu_pre_constexpr(command):
|
||||
return [
|
||||
Configuration(command + "-c++0x-noconstexpr-war",
|
||||
command + " -std=c++0x " +
|
||||
"-DBETTER_ENUMS_NO_CONSTEXPR", skip_cxx98),
|
||||
Configuration(command + "-strict-conversion",
|
||||
command + " -std=c++0x " +
|
||||
"-DBETTER_ENUMS_NO_CONSTEXPR " +
|
||||
"-DBETTER_ENUMS_STRICT_CONVERSION",
|
||||
skip_cxx98 + skip_strict),
|
||||
Configuration(command + "-c++98", command + " -std=c++98", skip_cxx98)
|
||||
]
|
||||
|
||||
def gnu_pre_enum_class(command):
|
||||
return [
|
||||
Configuration(command + "-c++0x-noconstexpr-war",
|
||||
command + " -std=c++0x " +
|
||||
"-DBETTER_ENUMS_NO_CONSTEXPR", skip_cxx98),
|
||||
Configuration(command + "-c++98", command + " -std=c++98", skip_cxx98)
|
||||
]
|
||||
|
||||
CONFIGURATIONS = \
|
||||
modern_gnu("clang++36") + \
|
||||
modern_gnu("clang++35") + \
|
||||
modern_gnu("clang++34") + \
|
||||
modern_gnu("clang++33") + \
|
||||
modern_gnu("g++51") + \
|
||||
modern_gnu("g++49") + \
|
||||
modern_gnu("g++48") + \
|
||||
modern_gnu("g++47") + \
|
||||
gnu_pre_constexpr("g++46") + \
|
||||
gnu_pre_constexpr("g++45") + \
|
||||
gnu_pre_constexpr("g++44") + \
|
||||
gnu_pre_enum_class("g++43")
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
load_expected_outputs()
|
||||
|
||||
run("cxxtestgen --error-printer -o %s %s" %
|
||||
(CXXTEST_GENERATED, CXXTEST_SOURCE))
|
||||
|
||||
for configuration in CONFIGURATIONS:
|
||||
configuration.make_all()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
x
Reference in New Issue
Block a user