diff --git a/.gitattributes b/.gitattributes index 2f6d494..856971c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ *.h linguist-language=C++ +*.tmpl linguist-language=HTML diff --git a/.gitignore b/.gitignore index 6d0d915..657b89d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ *.pyc *.exe scratch/ +doc/html/ doc-publish/ test/cxxtest/*.cc +test/platform/ diff --git a/README.md b/README.md index 24eeef9..132272e 100644 --- a/README.md +++ b/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!): antonbachin@yahoo.com -## 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 diff --git a/doc/ApiReference.md b/doc/ApiReference.md new file mode 100644 index 0000000..1453b2f --- /dev/null +++ b/doc/ApiReference.md @@ -0,0 +1,272 @@ +## API reference + +Table of contents + +### Overview + +The following declaration + + #include + 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 + +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 _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 _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 _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. diff --git a/doc/CompilerSupport.md b/doc/CompilerSupport.md new file mode 100644 index 0000000..c2d5319 --- /dev/null +++ b/doc/CompilerSupport.md @@ -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 +~~~ diff --git a/doc/ExtendingMacroLimits.md b/doc/ExtendingMacroLimits.md new file mode 100644 index 0000000..31b4210 --- /dev/null +++ b/doc/ExtendingMacroLimits.md @@ -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 ` 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=''` + - For VC++, `\DBETTER_ENUMS_MACRO_FILE=''` + + 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. diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..758c612 --- /dev/null +++ b/doc/Makefile @@ -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 diff --git a/doc/OptInFeatures.md b/doc/OptInFeatures.md new file mode 100644 index 0000000..1cab2fb --- /dev/null +++ b/doc/OptInFeatures.md @@ -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. diff --git a/doc/api.html b/doc/api.html deleted file mode 100644 index feb0875..0000000 --- a/doc/api.html +++ /dev/null @@ -1,938 +0,0 @@ - - - - - - - -Better Enums - Reference - - - - - - - - - - - - -
- - -

- - Better Enums 0.8.0 - -

-
- -
- -
- -

- The best way to get started with Better Enums is to read the - project page and browse - the commented - - examples. This page gives reference documentation. -

- -

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 declaration: -

- -
enum class Enum : underlying_type {A, B, C};
- -

- that is, it is an enumerated type with constants Enum::A, - Enum::B, and Enum::C, and is represented in memory by an - integer of type underlying_type. Just like with a built-in - enum class, it is possible to specify numeric values and aliases - for constants: -

- -
ENUM(Enum, underlying_type, A = 1, B, C, D = A);
- -

- Constant values are assigned by the compiler by exactly the same rules as - for a built-in enum, so in the above example, Enum::A == 1, - Enum::B == 2, Enum::C == 3, and Enum::D == 1. -

- -
- -
- - -

Member index

- - - - - - - - - - - - - - - -
- - - -
- -
- - -

- Internal enumerated type - index -

- - -

typename Enum::_Enumerated

- -

- The declared type Enum is built around an internal - C++ enumeration. Notionally, -

- -
ENUM(Enum, int, A, B, C);
- -

produces

- -
class Enum {
-    ...
-  public:
-    enum _Enumerated : int {A, B, C};
-    ...
-};
- -

- _Enumerated 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 - Enum::A is not a value of type Enum, but a value of type - Enum::_Enumerated, which is convertible to Enum, in most - cases implicitly. -

- -

- Note that _Enumerated is not a - C++11 - enum class. -

- - -

- implicit constructor constexpr Enum(_Enumerated) -

- -

- A converting constructor for promoting values of type _Enumerated - to values of type Enum. As mentioned above, this typically happens - automatically when you write a literal constant such as Enum::A in - a context where a value of type Enum is expected. For example: -

- -
void do_something(Enum value) { ... }
-
-do_something(Enum::A);  // converted silently
- - -

- global unary constexpr operator +(_Enumerated) -

- -

- For use when the compiler does not choose the implicit constructor above. - For example: -

- -
(Enum::A).to_string()
- -

- This expression does not compile because Enum::A is not an object, - and the compiler does not promote it. The promotion can be forced: -

- -
(+Enum::A).to_string()
- -

- This is easier to maintain than writing out a call to the converting - constructor: -

- -
((Enum)Enum::A).to_string()
- - -

- casting constexpr operator _Enumerated() const -

- -

- Enables implicit conversion of Enum values down to - _Enumerated. The only purpose of this is to make Enum - values directly usable in switch statements for compiler-supported - case checking: -

- -
switch(enum_value) {
-    case Enum::A: ...; break;
-    case Enum::B: ...; break;
-    case Enum::C: ...; break;
-}
- -

- It is, unfortunately, a hole in the type safety of Enum, since it - allows implicit conversions to integral types (Enum to - _Enumerated, then _Enumerated 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 - enum class. -

- -
- - - -
- - -

- Underlying integral type - index -

- - -

typename Enum::_Integral

- -

- An alias for the underlying type that Enum was declared with. For - example, if the declaration is -

- -
ENUM(Enum, uint32_t, A, B, C);
- -

- Then Enum::_Integral is the same as uint32_t. -

- - -

static constexpr Enum _from_integral(_Integral)

- -

- 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 throws - std::runtime_error. The check takes time linear in the - number of constants in Enum. -

- - -

- static constexpr Enum _from_integral_unchecked(_Integral) -

- -

- 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. -

- - -

- member constexpr - _Integral enum_value.to_integral() const -

- -

- Returns the numeric representation of an enum value. -

- - -

static constexpr bool _is_valid(_Integral)

- -

- Checks that the given numeric value represents one of the constants in - Enum, as in _from_integral. Complexity is linear - in the number of constants. -

- -

invariant sizeof(Enum) == sizeof(Enum::_Integral)

- -

invariant alignof(Enum) == alignof(Enum::_Integral)

- -
- - - -
- - -

- String conversions - index -

- - -

static constexpr Enum _from_string(const char*)

- -

- Returns the enum constant given by the string. For example: -

- -
Enum::_from_string("A") == Enum::A
- -

- Complexity is linear in the number of constants multiplied by the - length of the longest constant name. If the string does not name a constant, - throws std::runtime_error. -

- - -

static constexpr Enum _from_string_nocase(const char*)

- -

- The same as above, but lookup is case-insensitive. -

- - -

- member constexpr const char* enum_value.to_string() - const -

- -

- 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 - linear in the number of constants. If the string does not name a - constant, throws std::runtime_error. -

- - -

static constexpr const char *_name

- -

- The name of the type, i.e., for -

- -
ENUM(Enum, int, A, B, C);
- -

- Enum::_name == "Enum" -

- - -

static constexpr bool _is_valid(const char*)

- -

- Checks that the given string is the name of one of the constants in - Enum, as in _from_string, with the same complexity. -

- - -

static constexpr bool _is_valid_nocase(const char*)

- -

- The same as above, but corresponding to _from_string_nocase. -

- -
- - - -
- - -

- Iteration - index -

- - -

static constexpr _ValueIterable values

- -

- An iterable container with all the defined constant values, in declaration - order. Suitable for use with range-based for loops: -

- -
for (Enum value : Enum::_values) {
-    // Will iterate over Enum::A, Enum::B, Enum::C
-}
- -

class _ValueIterable::iterator

- -

- An iterator over defined constant values with a constexpr - dereference operator. Can be created explicitly by the constexpr - expressions -

- -
Enum::_values::begin()
-Enum::_values::end()
- - -

static constexpr _NameIterable names

- -

- An iterable container with the names of the defined constant values, in - declaration order. Suitable for use with range-based for loops: -

- -
for (const char *name : Enum::_names) {
-    // Will iterate over "A", "B", "C"
-}
- -

class _NameIterable::iterator

- -

- An iterator over defined constant names with a constexpr - dereference operator. Can be created explicitly by the constexpr - expressions -

- -
Enum::_names::begin()
-Enum::_names::end()
- -
- - - -
- - -

- Range properties - index -

- - -

static constexpr _Enumerated _first

- -

- The first defined constant. For example, in -

- -
ENUM(Enum, int, A = 1, B = 5, C = 0);
- -

Enum::_first == Enum::A

- - -

static constexpr _Enumerated _last

- -

- The last defined constant, i.e. Enum::C in the above example. -

- - -

static constexpr _Enumerated _min

- -

- The defined constant with the smallest numeric value, i.e. Enum::C - in the above example. -

- - -

static constexpr _Enumerated _max

- -

- The defined constant with the greatest numeric value, i.e. Enum::B - in the above example. -

- - -

static constexpr size_t _size

- -

- The number of constants defined, i.e. 3 in the above example. -

- - -

static constexpr _Integral _span

- -

- The numeric span of the constants defined, i.e. _max - _min + 1, - which is 6 in the above example. -

- -
- - - -
- - -

- Comparisons - index -

- -

- member constexpr bool operator ==(const Enum&) - const -

- -

- member constexpr bool operator ==(_Enumerated) - const -

- -

- member constexpr bool operator !=(const Enum&) - const -

- -

- member constexpr bool operator !=(_Enumerated) - const -

- -

- member constexpr bool operator <(const Enum&) - const -

- -

- member constexpr bool operator <(_Enumerated) - const -

- -

- member constexpr bool operator <=(const Enum&) - const -

- -

- member constexpr bool operator <=(_Enumerated) - const -

- -

- member constexpr bool operator >(const Enum&) - const -

- -

- member constexpr bool operator >(_Enumerated) - const -

- -

- member constexpr bool operator >=(const Enum&) - const -

- -

- member constexpr bool operator >=(_Enumerated) - const -

- -

- These define an ordering on values of types Enum and - Enum::_Enumerated. 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. -

- -
- - - -
- - -

- Additional type safety - index -

- -

default constructor Enum() = delete

- -

- The default constructor is deleted to encourage initialization with valid - values only and to avoid undefined states. See - - example/6-traits.cc for an example of how to add an explicit notion of - default value. -

- -

invariant no arithmetic

- -

Arithmetic operators are explicitly deleted.

- -

invariant no implicit conversion from integers

- -
- -
- -
- -
- Copyright © 2015. - Distributed under the BSD 2-clause license. See - - LICENSE. -
- - - - - diff --git a/doc/better-enums.css b/doc/better-enums.css new file mode 100644 index 0000000..4791b69 --- /dev/null +++ b/doc/better-enums.css @@ -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; +} diff --git a/doc/demo/101-special-values.md b/doc/demo/101-special-values.md new file mode 100644 index 0000000..7d35c13 --- /dev/null +++ b/doc/demo/101-special-values.md @@ -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 + #include + #include + +### 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() { 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 + constexpr Enum find_default() + { + return + Enum::_size < 2 ? + throw std::logic_error("enum has no valid constants") : + Enum::_values()[0] == find_invalid() ? + 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() { return Enum::Value; } + +Usage: + + static_assert(find_default() == +Channel::Red, ""); + static_assert(find_default() == +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 + struct assert_enum { + using check = typename Enum::_enumerated; + using type = Enum; + }; + + struct invalid_t { + template + constexpr operator To() const { return find_invalid(); } + + template + constexpr To convert() const { return find_invalid(); } + }; + + struct default_t { + template + constexpr operator To() const { return find_default(); } + }; + + 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! diff --git a/doc/demo/102-bitset.md b/doc/demo/102-bitset.md new file mode 100644 index 0000000..1a01a6a --- /dev/null +++ b/doc/demo/102-bitset.md @@ -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 + #include + + template + constexpr Enum max_loop(Enum accumulator, size_t index) + { + return + index >= Enum::_size ? accumulator : + Enum::_values()[index] > accumulator ? + max_loop(Enum::_values()[index], index + 1) : + max_loop(accumulator, index + 1); + } + + template + constexpr Enum max() + { + return max_loop(Enum::_values()[0], 1); + } + +And use that to declare a bit set template: + + template + using EnumSet = std::bitset()._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 channels; + EnumSet depths; + + return 0; + } diff --git a/doc/demo/103-quine.md b/doc/demo/103-quine.md new file mode 100644 index 0000000..cf39828 --- /dev/null +++ b/doc/demo/103-quine.md @@ -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 + #include + #include + + #ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING + #define BETTER_ENUMS_CONSTEXPR_TO_STRING + #endif + + #include + + #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 + constexpr size_t constants_length(size_t index = 0, size_t accumulator = 0) + { + return + index >= Enum::_size ? accumulator : + + constants_length( + index + 1, accumulator + + string_length(", ") + + string_length(Enum::_names()[index]) + + string_length(" = ") + + value_length( + Enum::_values()[index]._to_integral())); + } + + template + constexpr size_t declaration_length() + { + return + string_length("ENUM(") + + string_length(Enum::_name()) + + string_length(", int") + + constants_length() + + string_length(")"); + } + +Now, we can declare: + + char channel_definition[declaration_length() + 1]; + char depth_definition[declaration_length() + 1]; + +And finally, the formatting function: + + template + 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_definition); + assert(channel_length + 1 == sizeof(channel_definition)); + + size_t depth_length = format(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. diff --git a/doc/docs.py b/doc/docs.py new file mode 100755 index 0000000..35f7879 --- /dev/null +++ b/doc/docs.py @@ -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 = "\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() diff --git a/doc/index.html b/doc/index.html deleted file mode 100644 index 364a838..0000000 --- a/doc/index.html +++ /dev/null @@ -1,257 +0,0 @@ - - - - - - - -Better Enums - Clean Reflective Enums for C++ - - - - - - - - - - - - - - -
-

Better Enums

-

Clean compile-time reflective enums for - C++11

-

C++98 support coming soon without the "compile-time" :)

-
- -
-
-

- Have you noticed the awkward situation with enums in - C++? Built-in enum class 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 here. -

- -

- Better Enums gives you rich, reflective enums with the nicest syntax yet - seen.1 All you have to do is add one short - and simple header file to your project, and you are ready to go. -

- -
#include <enum.h>
-ENUM(Channel, int, Red, Green, Blue);
- -

- This gives you an int-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 "Channel" - itself. You can even assign explicit values to constants and alias them - like with a normal built-in enum. -

- -

- 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 - C++. -

- -

- Better Enums is free, available under the BSD license. The - author is committed to further development and support, so please - contact me, open an issue on - GitHub, or ask a - question on Stack Overflow. -

-
- -
-

Installation

- -

- Download - - enum.h and copy it to your project. That's it! Just make sure it's in - your include path and you are compiling with gcc - or clang in - C++11 mode. -

- -

- msvc support is coming in a near-future version - with some enum features disabled. This is because - msvc's support for constexpr is - lagging. The same patch will probably make it possible to use Better Enums - with C++98. -

-
- -
-

Tutorial

- -

- Create a file and put this code in it: -

- -
#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;
-}
- -

- Compile and run, and you should see the output - Channel Green has value 1. Congratulations! You have compiled - your first Better Enum. -

- -
- -

- Values are assigned to Better Enums just like to regular - C++ enums. That means you can change the enum - above to something like this: -

- -
ENUM(Channel, int, Red, Green = 5, Blue, Last = Blue);
- -

- and the result would be just as you'd expect: Red is 0, - Green is 5, Blue is 6, and Last is - also 6. -

- -
- -

- There is no need for Last, however. Every Better Enum comes - with a built-in value called _last. So, if you have -

- -
ENUM(Channel, int, Red, Green, Blue);
- -

- Then Channel::_last is Channel::Blue! In fact, - Channel also gets _first, _min, - _max, _span, and _size. These - built-in values have underscores so that you can define your own enums - without having to worry about naming problems: -

- -
ENUM(Position, int, first, last, other);
- -
- -

- 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: -

- -

Channel   my_channel = Channel::_from_string("Blue");
-Channel   my_channel = Channel::_from_integral(2);
- -

- If your code tries to convert an invalid string or integer to a - Channel, it will get an std::runtime_error - exception. -

- -
- -

- You can iterate over all the declared values of an enum: -

- -
for(Channel channel : Channel::_values)
-    std::cout << channel.to_string() << std::endl;
- -

- and directly over their names with Channel::_names. -

- -
- -

- 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 - examples - 2 - 3. -

-
- -
-

Where to go from here

- - -
-
- -
- Copyright © 2015 Anton Bachin. Released under the BSD 2-clause license. - See - LICENSE. -
- This page is part of the documentation for Better Enums 0.8.0. -
-
- 1 Yet seen by the author, of course :) -
- - - diff --git a/doc/index.md b/doc/index.md new file mode 100644 index 0000000..494be42 --- /dev/null +++ b/doc/index.md @@ -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! + +
+
+

$cxx11

+ +
#include <iostream>
+#include <enum.h>
+
+ENUM(Channel, int,
+     Red = 1, Green, Blue)
+
+int main()
+{
+  Channel c = Channel::Red;
+  std::cout << c._to_string();
+
+  for (Channel c : Channel::_values())
+    std::cout << c._to_string();
+
+  switch (c) {
+    case Channel::Red:
+      return c._to_integral();
+    case Channel::Green: return 15;
+    case Channel::Blue:  return 42;
+  }
+}
+
+constexpr Channel c =
+  Channel::_from_string("Blue");
+
+
+

$cxx98

+
#include <iostream>
+#include <enum.h>
+
+ENUM(Channel, int,
+     Red = 1, Green, Blue)
+
+int main()
+{
+  Channel c = Channel::Red;
+  std::cout << c._to_string();
+
+  for (size_t i = 0;
+       i < Channel::_size; ++i) {
+
+    c = Channel::_values()[i];
+    std::cout << c._to_string();
+  }
+
+  switch (c) {
+    case Channel::Red:
+      return c._to_integral();
+    case Channel::Green: return 15;
+    case Channel::Blue:  return 42;
+  }
+}
+
+ +
+
+ +To install, simply download enum.h 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. + +--- + +
+
+

Tutorials

+
    + $tutorial_toc +
+
+ +
+

Advanced demos

+
    + $demo_toc +
+
+
+ + + +
+ + + + + + + + + +%% title = Clean reflective enums for C++ diff --git a/doc/performance.md b/doc/performance.md new file mode 100644 index 0000000..e23314a --- /dev/null +++ b/doc/performance.md @@ -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. diff --git a/doc/style.css b/doc/style.css deleted file mode 100644 index 388b13e..0000000 --- a/doc/style.css +++ /dev/null @@ -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; -} diff --git a/doc/template/be.html b/doc/template/be.html new file mode 100644 index 0000000..3bd900b --- /dev/null +++ b/doc/template/be.html @@ -0,0 +1 @@ +Better Enums \ No newline at end of file diff --git a/doc/template/cxx.tmpl b/doc/template/cxx.tmpl new file mode 100644 index 0000000..9dc52e7 --- /dev/null +++ b/doc/template/cxx.tmpl @@ -0,0 +1 @@ +C++ \ No newline at end of file diff --git a/doc/template/cxx11.tmpl b/doc/template/cxx11.tmpl new file mode 100644 index 0000000..3954bc1 --- /dev/null +++ b/doc/template/cxx11.tmpl @@ -0,0 +1 @@ +C++11 \ No newline at end of file diff --git a/doc/template/cxx98.tmpl b/doc/template/cxx98.tmpl new file mode 100644 index 0000000..9c087c9 --- /dev/null +++ b/doc/template/cxx98.tmpl @@ -0,0 +1 @@ +C++98 \ No newline at end of file diff --git a/doc/template/demo.tmpl b/doc/template/demo.tmpl new file mode 100644 index 0000000..1cd1edc --- /dev/null +++ b/doc/template/demo.tmpl @@ -0,0 +1,6 @@ +

+ The code on this page is an advanced demo of Better Enums. You can + download it and try it out. +

+ +$demo_body diff --git a/doc/template/download.tmpl b/doc/template/download.tmpl new file mode 100644 index 0000000..46e6804 --- /dev/null +++ b/doc/template/download.tmpl @@ -0,0 +1,2 @@ +href="https://raw.githubusercontent.com/aantron/better-enums/$ref/enum.h" +download \ No newline at end of file diff --git a/doc/template/footer.tmpl b/doc/template/footer.tmpl new file mode 100644 index 0000000..d7b0d88 --- /dev/null +++ b/doc/template/footer.tmpl @@ -0,0 +1,12 @@ + + +
+ Copyright © 2015 Anton Bachin. Released under the BSD 2-clause license. + See + LICENSE. +
+ This page is part of the documentation for Better Enums $version. +
+ + + diff --git a/doc/template/header.tmpl b/doc/template/header.tmpl new file mode 100644 index 0000000..0e6d468 --- /dev/null +++ b/doc/template/header.tmpl @@ -0,0 +1,43 @@ + + + + + +$title + + + + + + + + + + + + + + +
 
+ +
+
{}
+ +

Better Enums

+

Fast, intuitive enums for $cxx

+

Open-source under the BSD license

+
+ +
diff --git a/doc/template/last.tmpl b/doc/template/last.tmpl new file mode 100644 index 0000000..4a5de7e --- /dev/null +++ b/doc/template/last.tmpl @@ -0,0 +1,6 @@ + diff --git a/doc/template/location.tmpl b/doc/template/location.tmpl new file mode 100644 index 0000000..c017893 --- /dev/null +++ b/doc/template/location.tmpl @@ -0,0 +1 @@ +http://aantron.github.io/better-enums \ No newline at end of file diff --git a/doc/template/next.tmpl b/doc/template/next.tmpl new file mode 100644 index 0000000..20d640c --- /dev/null +++ b/doc/template/next.tmpl @@ -0,0 +1,9 @@ + diff --git a/doc/template/page.tmpl b/doc/template/page.tmpl new file mode 100644 index 0000000..ba1a6dc --- /dev/null +++ b/doc/template/page.tmpl @@ -0,0 +1,5 @@ +$header + +$body + +$footer diff --git a/doc/template/project.tmpl b/doc/template/project.tmpl new file mode 100644 index 0000000..7510e41 --- /dev/null +++ b/doc/template/project.tmpl @@ -0,0 +1 @@ +Better Enums \ No newline at end of file diff --git a/doc/template/ref.tmpl b/doc/template/ref.tmpl new file mode 100644 index 0000000..8b25206 --- /dev/null +++ b/doc/template/ref.tmpl @@ -0,0 +1 @@ +master \ No newline at end of file diff --git a/doc/template/repo.tmpl b/doc/template/repo.tmpl new file mode 100644 index 0000000..cb19545 --- /dev/null +++ b/doc/template/repo.tmpl @@ -0,0 +1 @@ +https://github.com/aantron/better-enums \ No newline at end of file diff --git a/doc/template/tocitem.tmpl b/doc/template/tocitem.tmpl new file mode 100644 index 0000000..0fb051f --- /dev/null +++ b/doc/template/tocitem.tmpl @@ -0,0 +1 @@ +
  • $title
  • \ No newline at end of file diff --git a/doc/template/tutorial.tmpl b/doc/template/tutorial.tmpl new file mode 100644 index 0000000..3c1a07f --- /dev/null +++ b/doc/template/tutorial.tmpl @@ -0,0 +1,8 @@ +

    + Welcome to the Better Enums tutorials! The code in this tutorial forms a + valid program, which you can download and play with. +

    + +$tutorial_body + +$computed_next diff --git a/doc/template/version.tmpl b/doc/template/version.tmpl new file mode 100644 index 0000000..8b25206 --- /dev/null +++ b/doc/template/version.tmpl @@ -0,0 +1 @@ +master \ No newline at end of file diff --git a/doc/transform.py b/doc/transform.py new file mode 100755 index 0000000..4cad24a --- /dev/null +++ b/doc/transform.py @@ -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>", "", escaped) + replaced = re.sub("</em>", "", replaced) + replaced = re.sub("#(ifn?def|endif).*\n?", "", replaced) + + if lang == "comment": + start_tag = "
    "
    +        else:
    +            start_tag = "
    "
    +
    +        return start_tag + replaced + "
    " + + 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("", "", 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) diff --git a/doc/tutorial/1-hello-world.md b/doc/tutorial/1-hello-world.md new file mode 100644 index 0000000..fe639d1 --- /dev/null +++ b/doc/tutorial/1-hello-world.md @@ -0,0 +1,21 @@ +## Hello, World! + +Download enum.h, then build this program with it: + + #include + #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! diff --git a/doc/tutorial/2-conversions.md b/doc/tutorial/2-conversions.md new file mode 100644 index 0000000..f66d4d6 --- /dev/null +++ b/doc/tutorial/2-conversions.md @@ -0,0 +1,157 @@ +## Conversions + +Let's begin by including `enum.h` and declaring our enum: + + #include + #include + + #include + + 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 maybe_channel = + Channel::_from_string_nothrow("Yellow"); + + if (!maybe_channel) + std::cout << "error"; + else + std::cout << maybe_channel->_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 + 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 *representation type* `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 +Reference section has a full explanation. + +--- + +This concludes the first tutorial! + +--- + + std::cout << std::endl; + return 0; + } diff --git a/doc/tutorial/3-iterate.md b/doc/tutorial/3-iterate.md new file mode 100644 index 0000000..7d689bc --- /dev/null +++ b/doc/tutorial/3-iterate.md @@ -0,0 +1,47 @@ +## Iteration + +Better Enums makes it easy to iterate over the values you have declared. For +example, this: + + #include + #include + + 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: + +~~~comment + 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; + } diff --git a/doc/tutorial/4-switch.md b/doc/tutorial/4-switch.md new file mode 100644 index 0000000..8175423 --- /dev/null +++ b/doc/tutorial/4-switch.md @@ -0,0 +1,28 @@ +## Safe switch + +A Better Enum can be used directly in a `switch` statement: + + #include + #include + + 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; + } diff --git a/doc/tutorial/5-safety.md b/doc/tutorial/5-safety.md new file mode 100644 index 0000000..ab003d3 --- /dev/null +++ b/doc/tutorial/5-safety.md @@ -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 + #include + + 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: + +~~~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 +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. + + - 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. + +--- + + } diff --git a/doc/tutorial/6-representation.md b/doc/tutorial/6-representation.md new file mode 100644 index 0000000..6d0583e --- /dev/null +++ b/doc/tutorial/6-representation.md @@ -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 + #include + #include + + 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 *underlying* or *representation* 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; + } + +[^*]: 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. diff --git a/doc/tutorial/7-constexpr.md b/doc/tutorial/7-constexpr.md new file mode 100644 index 0000000..306db30 --- /dev/null +++ b/doc/tutorial/7-constexpr.md @@ -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 + + #ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING + #define BETTER_ENUMS_CONSTEXPR_TO_STRING + #endif + + #include + + 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. diff --git a/enum.h b/enum.h index d076735..c68a524 100644 --- a/enum.h +++ b/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__ diff --git a/example/1-basic.cc b/example/1-basic.cc deleted file mode 100644 index c302ab4..0000000 --- a/example/1-basic.cc +++ /dev/null @@ -1,110 +0,0 @@ -// Basic conversions to/from strings and the underlying integral type. - -#include -#include - -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(), - "the underlying integral type is accessible as a member"); diff --git a/example/1-hello-world.cc b/example/1-hello-world.cc new file mode 100644 index 0000000..cf40093 --- /dev/null +++ b/example/1-hello-world.cc @@ -0,0 +1,23 @@ +// This file was generated automatically + +// Hello, World! +// +// Download enum.h, then build this program with it: + +#include +#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! diff --git a/example/101-special-values.cc b/example/101-special-values.cc new file mode 100644 index 0000000..9a2adf9 --- /dev/null +++ b/example/101-special-values.cc @@ -0,0 +1,126 @@ +// This file was generated automatically + +// 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 +#include +#include + +// 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 +constexpr Enum find_invalid() { return Enum::Invalid; } + +#define OVERRIDE_INVALID(Enum, Value) \ +template<> \ +constexpr Enum find_invalid() { 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::Invalid, ""); +static_assert(find_invalid() == +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 +constexpr Enum find_default() +{ + return + Enum::_size < 2 ? + throw std::logic_error("enum has no valid constants") : + Enum::_values()[0] == find_invalid() ? + 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() { return Enum::Value; } + +// Usage: + +static_assert(find_default() == +Channel::Red, ""); +static_assert(find_default() == +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 +struct assert_enum { + using check = typename Enum::_enumerated; + using type = Enum; +}; + +struct invalid_t { + template + constexpr operator To() const { return find_invalid(); } + + template + constexpr To convert() const { return find_invalid(); } +}; + +struct default_t { + template + constexpr operator To() const { return find_default(); } +}; + +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! diff --git a/example/102-bitset.cc b/example/102-bitset.cc new file mode 100644 index 0000000..3c39317 --- /dev/null +++ b/example/102-bitset.cc @@ -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 +#include + +template +constexpr Enum max_loop(Enum accumulator, size_t index) +{ + return + index >= Enum::_size ? accumulator : + Enum::_values()[index] > accumulator ? + max_loop(Enum::_values()[index], index + 1) : + max_loop(accumulator, index + 1); +} + +template +constexpr Enum max() +{ + return max_loop(Enum::_values()[0], 1); +} + +// And use that to declare a bit set template: + +template +using EnumSet = std::bitset()._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 channels; + EnumSet depths; + + return 0; +} diff --git a/example/103-quine.cc b/example/103-quine.cc new file mode 100644 index 0000000..87aa91a --- /dev/null +++ b/example/103-quine.cc @@ -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 +#include +#include + +#ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING +#define BETTER_ENUMS_CONSTEXPR_TO_STRING +#endif + +#include + +#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 +constexpr size_t constants_length(size_t index = 0, size_t accumulator = 0) +{ + return + index >= Enum::_size ? accumulator : + + constants_length( + index + 1, accumulator + + string_length(", ") + + string_length(Enum::_names()[index]) + + string_length(" = ") + + value_length( + Enum::_values()[index]._to_integral())); +} + +template +constexpr size_t declaration_length() +{ + return + string_length("ENUM(") + + string_length(Enum::_name()) + + string_length(", int") + + constants_length() + + string_length(")"); +} + +// Now, we can declare: + +char channel_definition[declaration_length() + 1]; +char depth_definition[declaration_length() + 1]; + +// And finally, the formatting function: + +template +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_definition); + assert(channel_length + 1 == sizeof(channel_definition)); + + size_t depth_length = format(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. diff --git a/example/2-conversions.cc b/example/2-conversions.cc new file mode 100644 index 0000000..6368d4e --- /dev/null +++ b/example/2-conversions.cc @@ -0,0 +1,142 @@ +// This file was generated automatically + +// Conversions +// +// Let's begin by including enum.h and declaring our enum: + +#include +#include + +#include + +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 maybe_channel = + Channel::_from_string_nothrow("Yellow"); + + if (!maybe_channel) + std::cout << "error"; + else + std::cout << maybe_channel->_to_string() << " "; + +// This returns an optional value, in the style of boost::optional +// or the proposed std::optional. +// +// 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 representation type 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 Reference section has a full explanation. +// This concludes the first tutorial! + + std::cout << std::endl; + return 0; +} diff --git a/example/2-iterate.cc b/example/2-iterate.cc deleted file mode 100644 index 39a6543..0000000 --- a/example/2-iterate.cc +++ /dev/null @@ -1,29 +0,0 @@ -// Iteration over all constants. - -#include -#include - -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; -} diff --git a/example/3-iterate.cc b/example/3-iterate.cc new file mode 100644 index 0000000..46abe51 --- /dev/null +++ b/example/3-iterate.cc @@ -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 +#include + +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; +} diff --git a/example/3-switch.cc b/example/3-switch.cc deleted file mode 100644 index 61d3b98..0000000 --- a/example/3-switch.cc +++ /dev/null @@ -1,38 +0,0 @@ -// Switch case exhaustiveness checking. - -#include -#include - -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; -} diff --git a/example/4-constexpr.cc b/example/4-constexpr.cc deleted file mode 100644 index f931ca3..0000000 --- a/example/4-constexpr.cc +++ /dev/null @@ -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 -#include - -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; -} diff --git a/example/4-switch.cc b/example/4-switch.cc new file mode 100644 index 0000000..f45ac30 --- /dev/null +++ b/example/4-switch.cc @@ -0,0 +1,28 @@ +// This file was generated automatically + +// Safe switch +// +// A Better Enum can be used directly in a switch statement: + +#include +#include + +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; +} diff --git a/example/5-containers.cc b/example/5-containers.cc deleted file mode 100644 index 43f7e16..0000000 --- a/example/5-containers.cc +++ /dev/null @@ -1,64 +0,0 @@ -// Usage with STL containers. - -#include -#include -#include - -#include - -ENUM(Channel, int, Red, Green, Blue) - -int main() -{ - // Vectors of enums. - std::vector 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 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 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; -} diff --git a/example/5-safety.cc b/example/5-safety.cc new file mode 100644 index 0000000..f50d845 --- /dev/null +++ b/example/5-safety.cc @@ -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 +#include + +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 here 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 next +// tutorial. +// 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. + +} diff --git a/example/6-representation.cc b/example/6-representation.cc new file mode 100644 index 0000000..fbbb81c --- /dev/null +++ b/example/6-representation.cc @@ -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 +#include +#include + +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 underlying or +// representation 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; +} diff --git a/example/6-traits.cc b/example/6-traits.cc deleted file mode 100644 index ba7031b..0000000 --- a/example/6-traits.cc +++ /dev/null @@ -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 -#include - - - -// Adopt the convention that the first value in an enum is the default value. -template -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_() \ - { \ - 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_(); - std::cout << depth._to_string() << std::endl; - - std::cout << default_()._to_string() << std::endl; - std::cout << default_()._to_string() << std::endl; - - return 0; -} - - - -// Also works at compile-time. -constexpr auto value = default_(); diff --git a/example/7-bitset.cc b/example/7-bitset.cc deleted file mode 100644 index e39e1f3..0000000 --- a/example/7-bitset.cc +++ /dev/null @@ -1,44 +0,0 @@ -// Usage with std::bitset. - -#include -#include -#include - -// Computes the maximum value of an enum at compile time. -template -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()._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; -} diff --git a/example/7-constexpr.cc b/example/7-constexpr.cc new file mode 100644 index 0000000..351cb68 --- /dev/null +++ b/example/7-constexpr.cc @@ -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 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 + +#ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING +#define BETTER_ENUMS_CONSTEXPR_TO_STRING +#endif + +#include + +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. diff --git a/example/8-constexpr-iterate.cc b/example/8-constexpr-iterate.cc deleted file mode 100644 index 58b3775..0000000 --- a/example/8-constexpr-iterate.cc +++ /dev/null @@ -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 -#include -#include -#include - -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 -constexpr size_t total_names_length(size_t accumulator = 0, size_t index = 0) -{ - return - index == Enum::_size ? accumulator : - total_names_length - (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 -constexpr size_t declaration_length() -{ - return - 4 + 1 + string_length(Enum::_name()) + 1 + 1 + 3 + 1 + 1 + - total_names_length() + (Enum::_size - 1) * 2 + 1 + 1 + 1; -} - -// Formats the declaration into space already reserved. -template -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() - 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()]; -char depth_declaration[declaration_length()]; - - - -int main() -{ - format_declaration(channel_declaration); - std::cout << channel_declaration << std::endl; - - format_declaration(depth_declaration); - std::cout << depth_declaration << std::endl; - - return 0; -} diff --git a/script/make_macros.py b/script/make_macros.py index 58cf572..b08636e 100755 --- a/script/make_macros.py +++ b/script/make_macros.py @@ -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='' -# - for VC++, /BETTER_ENUMS_MACRO_FILE='' +# - for gcc and clang, -DBETTER_ENUMS_MACRO_FILE='' +# - for VC++, /DBETTER_ENUMS_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. diff --git a/test/Makefile b/test/Makefile index 426d3be..6a9e0c6 100644 --- a/test/Makefile +++ b/test/Makefile @@ -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 diff --git a/test/cxxtest/tests.h b/test/cxxtest/tests.h index a56f0bc..4b9008a 100644 --- a/test/cxxtest/tests.h +++ b/test/cxxtest/tests.h @@ -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())); // Supported constructors. -static_assert_1(!std::is_default_constructible()); #ifdef __clang__ static_assert_1(std::is_trivially_copyable()); @@ -61,6 +60,9 @@ static_assert_1((std::is_constructible())); static_assert_1(!(std::is_constructible())); static_assert_1(!(std::is_constructible())); +// Commented out temporarily due to GCC 4.7- bug. +// static_assert_1(!std::is_default_constructible()); + // 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 diff --git a/test/expect/1-hello-world b/test/expect/1-hello-world new file mode 100644 index 0000000..8ab686e --- /dev/null +++ b/test/expect/1-hello-world @@ -0,0 +1 @@ +Hello, World! diff --git a/test/expect/101-special-values b/test/expect/101-special-values new file mode 100644 index 0000000..d30c108 --- /dev/null +++ b/test/expect/101-special-values @@ -0,0 +1 @@ +Red diff --git a/test/expect/102-bitset b/test/expect/102-bitset new file mode 100644 index 0000000..e69de29 diff --git a/test/expect/103-quine b/test/expect/103-quine new file mode 100644 index 0000000..437eca3 --- /dev/null +++ b/test/expect/103-quine @@ -0,0 +1,2 @@ +ENUM(Channel, int, Red = 0, Green = 1, Blue = 2) +ENUM(Depth, int, TrueColor = 1, HighColor = 0) diff --git a/test/expect/2-conversions b/test/expect/2-conversions new file mode 100644 index 0000000..d6e1a38 --- /dev/null +++ b/test/expect/2-conversions @@ -0,0 +1 @@ +Cyan Magenta Yellow Cyan 1 Magenta Cyan diff --git a/test/expect/3-iterate b/test/expect/3-iterate new file mode 100644 index 0000000..67fd5fe --- /dev/null +++ b/test/expect/3-iterate @@ -0,0 +1,2 @@ +0 2 3 +Red Green Blue diff --git a/test/expect/4-switch b/test/expect/4-switch new file mode 100644 index 0000000..81b5c5d --- /dev/null +++ b/test/expect/4-switch @@ -0,0 +1 @@ +37 diff --git a/test/expect/5-safety b/test/expect/5-safety new file mode 100644 index 0000000..e69de29 diff --git a/test/expect/6-representation b/test/expect/6-representation new file mode 100644 index 0000000..e3d3dc8 --- /dev/null +++ b/test/expect/6-representation @@ -0,0 +1 @@ +Comment diff --git a/test/expect/7-constexpr b/test/expect/7-constexpr new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/test/expect/7-constexpr @@ -0,0 +1 @@ +5 diff --git a/test/test.py b/test/test.py new file mode 100755 index 0000000..8dddf3a --- /dev/null +++ b/test/test.py @@ -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()