diff --git a/README.md b/README.md index 0bf6565..74cd18e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Better Enums -Reflective compile-time C++ enum library with clean syntax. For example: +Reflective compile-time C++ enum library with clean syntax, in a single header +file. For example: + #include ENUM(Channel, int, Red = 1, Green, Blue) defines a type `Channel`. You can then do natural things such as: @@ -10,56 +12,73 @@ defines a type `Channel`. You can then do natural things such as: Channel channel = Channel::Green; 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 (C++11 only) +Channel::_from_integral(3); // Checked cast, Channel::Blue + +Channel::_size; // Number of channels (3) +Channel::_values()[0]; // Get the first channel for (Channel channel : Channel::_values()) { - // Iterate over all channels + process(channel); // Iterate over all channels +} + +// Natural switch, compiler checks the cases +switch (channel) { + case Channel::Red: break; + case Channel::Green: break; + case Channel::Blue: break; } ``` -...and more. See the [project page](http://aantron.github.io/better-enums). +...and more. + +In C++11, *everything* is available at compile time. You can convert your enums, +loop over them, [find their max][max], +[statically enforce conventions][enforce], and pass along the results as +template arguments or to `constexpr` functions. All the reflection is available +for your metaprogramming needs. + +The interface is the same for C++98 — you just have to use most of it at +run time only. This library does provide scoped and sized enums, something not +built into C++98. + +See the [project page][project] for full documentation. + +[max]: http://aantron.github.io/better-enums/demo/BitSets.html +[enforce]: http://aantron.github.io/better-enums/demo/SpecialValues.html +[project]: http://aantron.github.io/better-enums ## Installation -Simply add `enum.h` to your project. +Simply add `enum.h` to your project — that's it. -## Features +Then, include it and use the `ENUM` macro. Your compiler will generate the rich +enums that are missing from standard C++. -- Requires no external utility. -- Safe conversions to/from integers and strings. -- Iteration over declared values. -- Switch case checking. -- All operations are `constexpr` and can be used at compile time in your own - `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.3 to 5.1, clang 3.3 to 3.6, and VS2013, VS2015. +## Additional features + +- No dependencies and no external build tools. Uses only standard C++, though, + for C++98, variadic macro support is required. +- Supported and tested on clang, gcc, and msvc. +- Fast compilation. You have to declare a few dozen enums to slow down your + compiler as much as [just including `iostream` does][performance]. +- Use any initializers, just like with a built-in enum. +- Guaranteed size and alignment — you choose the representation type. + +[performance]: http://aantron.github.io/better-enums/Performance.html ## Contact -Don't hesitate to contact me about features (or bugs!): -antonbachin@yahoo.com +Don't hesitate to contact me about features or bugs: +[antonbachin@yahoo.com](mailto:antonbachin@yahoo.com), or open an issue on +GitHub. -## License +## License and history Better Enums is released under the BSD 2-clause license. See [LICENSE](https://github.com/aantron/better-enums/blob/master/LICENSE). -## History - The library was originally developed by the author in the winter of 2012-2013 at Hudson River Trading, as a replacement for an older generator called `BETTER_ENUM`. diff --git a/doc/ApiReference.md b/doc/ApiReference.md index 1453b2f..f0ef885 100644 --- a/doc/ApiReference.md +++ b/doc/ApiReference.md @@ -1,272 +1,370 @@ ## API reference -Table of contents +$internal_toc ### Overview -The following declaration +The declaration #include - ENUM(Enum, underlying_type, A, B, C, ...); + ENUM(Enum, underlying_type, A, B, C, ...) -generates a new type `Enum`. It is notionally similar to the type created by -this $cxx11 declaration: +generates a new class type `Enum` which is notionally similar to the type +created by this $cxx11 declaration: - enum class Enum : underlying_type {A, B, C, ...}; +~~~comment +enum class Enum : underlying_type {A, B, C, ...}; +~~~ -that is, `Enum` is a scoped enumerated type with constants `Enum::A`, `Enum::B`, +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, ...); + ENUM(Enum, underlying_type, A = 1, B = constant_expression, C = A, ...) -The initializers have the same meaning and constraints as in an `enum class` -declaration. +The initializers have the same meaning and constraints as in a built-in `enum` +or `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__`, + - `ENUM` is available for $cxx98 + [compilers](${prefix}CompilerSupport.html) supporting `__VA_ARGS__` — + all major compilers — while `enum class` is restricted to $cxx11, - the `ENUM` type is implicitly convertible to integral types, though this can - be disabled when using $cxx11, [How]() and + be [disabled](${prefix}OptInFeatures.html#StrictConversions) when using + $cxx11, 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); +The types produced by the `ENUM` macro are called *Better Enums* in the rest of +this reference. -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. +Better Enums are similar to their underlying type for the purposes of argument +passing. This means that they typically fit into a machine 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 +### Running example -#### typedef _enumerated +The rest of this reference uses the following declaration as a running example: -An internal type used to declare constants. `ENUM` generates something similar -to + ENUM(Enum, int, A, B, C) - 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) +### Helper functions and types -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`. +The types and functions described here make it possible to use Better Enums with +the rest of $cxx in a reasonable fashion, or else they are referenced in the +rest of the documentation. -#### constexpr implicit constructor Enum(_enumerated) +#### typedef _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 +An internal type used to declare constants. The `ENUM` macro generates something +similar to - Enum value = Enum::A; +~~~comment +struct Enum { + enum _enumerated : int {A, B, C}; + // ... +}; +~~~ + +The user needs to be aware of `_enumerated` 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 constant, as in +`Enum::A._to_string()`. This problem is addressed by operator `+` +[below](#Operator+). + +#### non-member constexpr Enum unary operator +(_enumerated) + +Forces promotion of [`Enum::_enumerated`](#Typedef_enumerated) to `Enum`. +Provided to solve the problem described [above](#Typedef_enumerated). So: + + // Does not compile + Enum::A._to_string() + + // Compiles + (+Enum::A)._to_string() + +#### constexpr implicit constructor Enum(_enumerated) + +A constructor that performs implicit conversions of +[`Enum::_enumerated`](#Typedef_enumerated) to `Enum`. This allows code to use a +literal constant where `Enum` is expected, and the compiler can do an implicit +conversion. For example: + + void do_something(Enum value); + + do_something(+Enum::A); // Not necessary + do_something(Enum::A); // Implicit conversion available + + Enum value = Enum::A; // Implicit conversion The other constructors of `Enum` are the implicitly-generated copy and move -constructors. There is no default constructor. +constructors. There is no default constructor. If you have comments on what a +default constructor should do, please [let me know](${prefix}Contact.html). -#### static constexpr size_t _size +#### non-member struct better_enums::optional<Enum> + +An optional `Enum` value. These are returned by the various `_nothrow` +functions, such as [`_from_string_nothrow`](#_from_string_nothrow). This type is +meant to represent the possibility of failure. For example, suppose you have: + + better_enums::optional<Enum> maybe = _from_string_nothrow("A"); + +An optional value such as `maybe` is convertible to `bool`. If it converts to +`true`, it holds a valid `Enum` value. Otherwise, if it converts to `false`, the +operation that produced the optional value failed. So, you can continue with + + if (maybe) { + // The string conversion succeeded + do_something(*maybe); + } + else { + // The string conversion failed + } + +As you can see, `*maybe` evaluates to the `Enum` value, in this case `Enum::A`. + +The rest of this reference refers to this type as simply `optional`, as if you +had entered + +~~~comment +using optional = better_enums::optional<Enum>; +~~~ + + + +### Value count and iteration + +The types and members described here have to do with the sequence of constants +declared, i.e. `A`, `B`, `C` in the [running example](#RunningExample). + +#### static constexpr size_t _size The number of constants declared. `Enum::_size == 3`. -#### typedef _value_iterable +#### 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`. +`constexpr` `begin()`, `end()`, and `size()` methods, and `constexpr` +`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`](#_values). -#### typedef _value_iterator +#### 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`. +Random-access iterator type for [`_value_iterable`](#_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`. You typically don't have to refer to this type +directly. -#### static constexpr _value_iterable _values() +#### static constexpr _value_iterable _values() -`constexpr` access to the collection of declared constants. For example: +`constexpr` access to the sequence of declared constants. For example: - for (size_t index = 0; index < Enum::_values().size(); ++index) - output(Enum::_values()[index]); + for (size_t index = 0; index < Enum::_values().size(); ++index) + do_something(Enum::_values()[index]); or, using iterators: - for (Enum::_value_iterator iterator = Enum::_values().begin(); - iterator != Enum::_values().end(); ++iterator) { + for (Enum::_value_iterator iterator = Enum::_values().begin(); + iterator != Enum::_values().end(); ++iterator) { - output(*iterator); + do_something(*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. + for (Enum value : Enum::_values()) + do_something(value); -### Strings +### String conversion and iteration -#### member constexpr? const char* _to_string() const +#### 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"`. +Returns the string representation a Better Enum value. For example: -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. + Enum value = Enum::A; + value._to_string(); // Same as "A". + +If two or more constants have the same numeric value, it is undefined which name +`_to_string` will choose, but it will choose one of them. If `value` is not equal to the representation of any declared constant, for -example if it was obtained using an unchecked cast such as: +example if it was obtained using an unchecked conversion such as - Enum value = Enum::_from_integral_unchecked(0xbadc0de); + 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. +Running 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. +This method is not `constexpr` by default. Read +[here](${prefix}OptInFeatures.html#CompileTimeNameTrimming) for information +about making it `constexpr`. -#### static constexpr Enum _from_string(const char*) +#### 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. +If the given string is the exact name of a declared constant, returns the +constant. 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*) +#### 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. +Same as [`_from_string`](#_from_string), but does not throw an exception on +failure. Returns an [optional value](#StructBetter_enumsoptional) instead. -#### static constexpr Enum _from_string_nocase(const char*) +#### 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. +Same as [`_from_string`](#_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*) +#### static constexpr optional _from_string_nocase_nothrow(const char*) -Is to `_from_string_nocase` as `_from_string_nothrow` is to `_from_string`. +Is to [`_from_string_nocase`](#_from_string_nocase) as +[`_from_string_nothrow`](#_from_string_nothrow) is to +[`_from_string`](#_from_string). -#### static constexpr bool _is_valid(const char*) +#### 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`. +declared constant. Running time is the same as for +[`_from_string`](#_from_string). -#### static constexpr bool _is_valid_nocase(const char*) +#### 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`. +The same as [`_is_valid`](#_is_validconstChar*), but comparison is done up to +case as in [`_from_string_nocase`](#_from_string_nocase). -#### static constexpr const char* _name() +#### 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 +#### 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`. +available, but is `constexpr` if and only if [`_to_string`](#_to_string) is +`constexpr`. Iteration visits constants in order of declaration. See usage +example under [`_names`](#_names). -#### typedef _name_iterator +#### 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. +`constexpr`, but dereferencing is `constexpr` if and only if +[`_to_string`](#_to_string) is `constexpr`. Mutating operators such as +`operator++` are not `constexpr` due to their nature — adding `1` is a +`constexpr` alternative. You typically don't have to refer to this type +directly. -#### static constexpr? _name_iterable _names() +#### static constexpr? _name_iterable _names() -Access to the collection of declared constant names. For example: +Access to the sequence of declared constant names. For example: - for (size_t index = 0; index < Enum::_names().size(); ++index) - std::cout << Enum::_names()[index] << std::endl; + 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) { + for (Enum::_name_iterator iterator = Enum::_names().begin(); + iterator != Enum::_names().end(); ++iterator) { - std::cout << *iterator << std::endl; + std::cout << *iterator << std::endl; } or, in $cxx11: - for (const char *name : Enum::_names()) - std::cout << name << std::endl; + for (const char *name : Enum::_names()) + std::cout << name << std::endl; -`constexpr` if and only if `_to_string` is `constexpr`. +`constexpr` if and only if [`_to_string`](#_to_string) is `constexpr`. -### Integers +### Integer conversion -#### typedef _integral +Better Enums are already represented as integers at run time. Values of the +[running example](#RunningExample) type `Enum` are the same as `ints`. However, +`Enum` is a distinct type from `int` during type checking, the main difference +being that its range of valid values is restricted to only the ones you have +declared. + +This section describes the various translations between `Enum` and `int` that +are available. Each one translates the type, but at run time, most are no-ops, +or validity checks followed by no-ops. + +#### 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. +and alignment requirement as its representation type. -#### member constexpr _integral _to_integral() const +#### 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`. +example, -#### static constexpr Enum _from_integral(_integral) + (+Enum::C)._to_integral() == 2 + +Note that Better Enums are already implicitly convertible to their underlying +integral types [by default](${prefix}OptInFeatures.html#StrictConversion). +You may still want to use this function, however, for clarity, and to ensure +that your code remains compatible if the strict conversions feature is enabled +later. + +#### 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) + Enum::_from_integral(2); // Enum::C + Enum::_from_integral(42); // std::runtime_error + +#### static constexpr optional _from_integral_nothrow(_integral) + +Checked conversion as [`_from_integral`](#_from_integral), but does not throw an +exception on failure. Returns an [optional value](#StructBetter_enumsoptional) +instead. + +#### 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) +This is the direct inverse of [`_to_integral`](#_to_integral). Here are no-op +round trips between `int` and `Enum`: -Checked conversion as `_from_integral`, but does not throw an exception on -failure. Returns an [optional]() value instead. + Enum::_from_integral_unchecked(value._to_integral()); + Enum::_from_integral_unchecked(integer)._to_integral(); -#### static constexpr bool _is_valid(_integral) +You should not use this function on untrusted input, however. -Evaluates to `true if and only if the given integer is the numeric value of one -of the declared constants. +#### 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. Running time is linear in the number of declared +constants. + + + +%% class = api + +%% description = Detailed description of the Better Enums API. diff --git a/doc/CompilerSupport.md b/doc/CompilerSupport.md index 4159a5b..4d0fe6d 100644 --- a/doc/CompilerSupport.md +++ b/doc/CompilerSupport.md @@ -1,23 +1,73 @@ ## Compiler support -Better Enums aims to support as many compilers as is reasonable. It has been -tested with clang++ and g++, and Visual C++: +Better Enums aims to support all major compilers. It is known to definitely work +on - - clang++ 3.3 to 3.6 - - g++ 4.3 to 5.1 - - Visual C++ 2013 Update 4, Visual C++ 2015 RC + - clang 3.3 to 3.6 + - gcc 4.3 to 5.1 + - Visual C++ 2013U4, 2015RC. -In principle, Better Enums can be used with any compiler that supports either +The library can be used with any compiler that supports either $cxx11, or $cxx98 +with the `__VA_ARGS__` extension. This includes every version of gcc and clang I +have ever heard of, and Visual C++ down to 2005. - - $cxx11 - - $cxx98 with the variadic macro (`__VA_ARGS__`) extension +To ensure that nothing is broken, every release of Better Enums is +[tested]($repo/tree/master/test) in multiple configuratins on the compilers +listed above. Testing includes the code in the tutorials, the unit tests, and a +multiple translation unit linking test. The full list of tested compilers and +configurations is given at [the end of this page](#TestedConfigurations). -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. +### Compile-time reflection configurations -The tests include compiling and running unit tests, all the examples in the -demos and tutorials, and a multiple translation unit linking test. +Read this section if: + + - you want to use Better Enums reflection at compile time, and need to know + exactly what features are supported on your compiler, or + - Better Enums is choosing the wrong configuration automatically and you need + to force a different choice. + +All features of Better Enums are always available for run-time use. However, for +compile-time use, Better Enums has two main configurations: $cxx98 mode and +`constexpr` mode. Better Enums tries to detect which compiler is compiling it +and select the appropriate mode. + +For performance reasons, `constexpr` mode is subdivided into a "fast" +`constexpr` mode and an +[opt-in](${prefix}OptInFeatures.html#CompileTimeNameTrimming) "full" (slow) +`constexpr` mode. The three modes can be ranked, with each next mode including +all the features of the preceding ones: + + - $cxx98 + - fast `constexpr` + - full `constexpr` + +Only `_size` is supported at compile time in $cxx98 mode. Fast `constexpr` mode +adds all other members besides `_to_string` and `_names`. Full `constexpr` mode +supports those at compile time as well. + +--- + +The mode selection code works as follows: + + - First, as of the time of this writing, only clang and gcc support + `constexpr`. So, if you are using any other compiler, Better Enums is in + $cxx98 mode. + - If you are using gcc 4.7 or higher or clang, Better Enums uses those + compilers' predefined macros to detect whether `constexpr` support is + enabled with compiler flags. If so, it is in one of the `constexpr` mode. + Otherwise, it falls back to $cxx98 mode. + - The default `constexpr` mode is fast `constexpr`. If you want to enable + full `constexpr` mode for some or all of your enums, follow + [these](${prefix}OptInFeatures.html#CompileTimeNameTrimming) instructions. + +If Better Enums picks the wrong mode, you can force `constexpr` mode by defining +`BETTER_ENUMS_CONSTEXPR` before including `enum.h`, typically by passing an +option to your compiler, or you can force $cxx98 mode by defining +`BETTER_ENUMS_NO_CONSTEXPR`. + +If you are using a compiler for which Better Enums makes the wrong choice, +please [let me know](${prefix}Contact.html). I will fix it and you won't have to +define these macros anymore. ### Tested configurations @@ -70,3 +120,5 @@ g++44 -std=c++98 g++43 -std=c++0x -DBETTER_ENUMS_NO_CONSTEXPR g++43 -std=c++98 ~~~ + +%% description = Information about compiler support and feature detection. diff --git a/doc/Contact.md b/doc/Contact.md new file mode 100644 index 0000000..b4b94e7 --- /dev/null +++ b/doc/Contact.md @@ -0,0 +1,15 @@ +## Contact + +- Send me an email: [antonbachin@yahoo.com](mailto:antonbachin@yahoo.com). +- Visit the [GitHub]($repo) project to open an issue or get a development + version. + +I also watch the `enums` tag on Stack Overflow. + +I'm happy to hear any feedback. If you have any trouble using the library or +parsing the documentation, please don't hesitate to let me know. + +And, if you find this library helpful, give it a star on GitHub to let me know +you use it. It will help keep me encouraged :) + +%% description = Contact information for bugs, issues, support, and feedback. diff --git a/doc/ExtendingLimits.md b/doc/ExtendingLimits.md new file mode 100644 index 0000000..d1e8950 --- /dev/null +++ b/doc/ExtendingLimits.md @@ -0,0 +1,40 @@ +## Extending limits + +The `ENUM` macro makes heavy use of the preprocessor, and some of the internal +macros have size limits. There are two: on the number of constants you can +declare, and on the maximum length of a constant name under very specific +conditions. If you run into either one, you can extend the limit by following +the instructions on this page. + +The second limit, on the maximum length of a constant name, applies only when +you are compiling an enum in +["full" `constexpr`](${prefix}OptInFeatures.html#CompileTimeNameTrimming) mode +*and* the constant has an initializer. Otherwise, your constants can have names +of arbitrary length. + +The default limits are 64 constants in an enum and 23 characters for initialized +constants of full-`constexpr` enums. To extend: + + 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 full 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` in your project. + 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 full-`constexpr` enums. + 7. You don't need `make_macros.py` anymore. It's not part of your build + process and you can delete it. + +%% description = How to extend limits imposed by internal macros. diff --git a/doc/ExtendingMacroLimits.md b/doc/ExtendingMacroLimits.md deleted file mode 100644 index 31b4210..0000000 --- a/doc/ExtendingMacroLimits.md +++ /dev/null @@ -1,41 +0,0 @@ -## 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/OptInFeatures.md b/doc/OptInFeatures.md index 1cab2fb..8f24518 100644 --- a/doc/OptInFeatures.md +++ b/doc/OptInFeatures.md @@ -1,34 +1,41 @@ ## 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. +Better Enums has two opt-in features. They are both "good," but they either hurt +compilation time or break compatibility with $cxx98, so they are disabled by +default. Read this page if you want to enable them. + +$internal_toc + +### Compile-time name trimming + +This makes `_to_string` and `_names` `constexpr`, i.e. "full" `constexpr` mode. +To enable this for all enums, define `BETTER_ENUMS_CONSTEXPR_TO_STRING` before +including `enum.h`. Typically, you would pass an option to your compiler to do +this. + +You can also enable this feature for individual enums instead, by declaring them +using the alternative `SLOW_ENUM` macro. + +The feature is disabled because it increases compilation times by a factor of +about 4. Compilation is still relatively fast — you need about a dozen +slow enums to get the same penalty as including `iostream` — but it is +a steep penalty nonetheless. I don't think most people need this feature most of +the time, so it's too high a price to pay. If I improve compilation times to the +point where compile-time name trimming can be the default, I will simply +redefine `SLOW_ENUM` as `ENUM` and deprecate it, so your code will still work. ### 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 +This disables implicit conversions to underlying integral types. At the moment, +you can only enable this globally for all enums, by defining +`BETTER_ENUMS_STRICT_CONVERSION` before including `enum.h`. - 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 reason Better Enums have implicit conversions to integral types in the first +place is that in $cxx98, Better Enums have to be implicitly convertible to their +member [`_enumerated`](${prefix}ApiReference.html#Typedef_enumerated) types to +be [usable](${prefix}tutorial/SafeSwitch.html) in `switch` statements. It is +possible to avoid this in $cxx11 and convert to `enum class` types instead, but +at the cost of breaking interface compatibility with $cxx98. - 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 @@ -39,71 +46,23 @@ so would break compatibility with the $cxx98 interface. 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. + strict, are mutually exclusive. They differ by a `+` character. Here they are: // Default variant - switch (channel) { + 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; + 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. +%% description = Opting into features disabled by default for performance or +compatibility reasons. diff --git a/doc/Performance.md b/doc/Performance.md new file mode 100644 index 0000000..d827810 --- /dev/null +++ b/doc/Performance.md @@ -0,0 +1,32 @@ +## Performance + +A basic performance test is run on +[every compiler tested](${prefix}CompilerSupport.html#TestedConfigurations). It +doesn't try to be very accurate — it just stress-tests the compiler once +to get a rough idea of how long it takes to compile Better Enums. + +The files compared in the test are as follows: + + - [One file]($repo/blob/$version/test/performance/4-declare_enums.cc) includes + `enum.h` and declares 647 constants across 36 Better Enums. + - The [other file]($repo/blob/$version/test/performance/5-iostream.cc) *only* + includes `iostream` and does nothing with it. + +The argument is that if compiling a bunch of Better Enums is faster, or about as +fast as, including a single standard header such as `iostream`, then Better +Enums is fast enough for general use. + +Results are given for select compilers and +[configurations](${prefix}CompilerSupport.html#CompileTimeReflectionConfigurations) +as ratios of how long it took to compile the Better Enums file to how long it +took to compile the `iostream` file. The less the ratio, the better. Ratios less +than 1 mean the enums compiled faster, and ratios greater than 1 mean `iostream` +compiled faster. + + - clang 3.6, fast `constexpr`: 0.66 + - clang 3.6, full `constexpr`: 2.25 + - gcc 5.1, fast `constexpr`: 1.58 + - gcc 5.1, full `constexpr`: 4.23 + - VC2015RC, $cxx98: 1.18 + +%% description = Compilation performance testing results. diff --git a/doc/better-enums.css b/doc/better-enums.css index 4791b69..721d361 100644 --- a/doc/better-enums.css +++ b/doc/better-enums.css @@ -6,7 +6,7 @@ body { font-weight: 300; } -pre, code, samp { +pre, code, samp, h4, .contents ul { font-family: Consolas, "Liberation Mono", Menlo, "Courier New", Courier, monospace; } @@ -17,7 +17,7 @@ pre { border-radius: 5px; overflow: scroll; color: rgba(255, 255, 255, 0.6); - font-size: 14px; + font-size: 78%; } pre em { @@ -41,38 +41,25 @@ code, samp { background-color: #EEF4F9; padding: 1px 3px; border-radius: 3px; - font-size: 14px; + font-size: 78%; } -nav > *, header > *, main, footer { +.container { max-width: 760px; - padding-left: 230px; + margin-left: 230px; } -@media(max-width: 1240px) { - nav > *, header > *, main > *, footer { - padding-left: 150px; +@media (max-width: 1220px) { + .container { + margin-left: auto; + margin-right: auto; } } -@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; +@media (max-width: 780px) { + .container { + margin-left: 10px; + margin-right: 10px; } } @@ -93,15 +80,24 @@ nav, .spacer { } nav a { - margin-left: 2em; + margin-right: 2em; } nav a.first { - margin-left: 0; font-weight: 600; font-size: 16px; } +@media (max-width: 560px) { + nav { + position: initial; + } + + .spacer { + display: none; + } +} + header { background-color: #4C6F8C; background: linear-gradient(#395E7E, #4A79A0); @@ -116,7 +112,7 @@ h1 { font-weight: 100; } -header > h2 { +header h2 { margin: 0; font-size: 24px; font-weight: 100; @@ -124,7 +120,7 @@ header > h2 { left: 3px; } -header > h3 { +header h3 { margin: 0; font-size: 14px; font-weight: 300; @@ -170,7 +166,7 @@ header a:hover { text-decoration: none; } -main a[href], footer a[href] { +.main a[href], footer a[href] { background-color: #edd; color: #844; letter-spacing: -0.5px; @@ -217,7 +213,7 @@ span#note:target { margin-left: 10px; } -main > h3 { +.main h3 { font-size: 30px; font-weight: 100; margin-top: 2em; @@ -230,6 +226,10 @@ h3 { font-weight: inherit; } +h3.contents { + font-size: 22px; +} + .pane pre { font-size: 14px; padding-top: 20px; @@ -238,13 +238,16 @@ h3 { } header { - position: relative; overflow: hidden; } +header .container { + position: relative; +} + div.back { position: absolute; - bottom: -0.2em; + bottom: -0.35em; left: -40px; font-size: 288px; font-weight: bold; @@ -265,15 +268,160 @@ div.back { margin-top: 4em; } -.tutorial-footer.next { +.tutorial-footer .next { font-weight: 100; font-size: 24px; } -.tutorial-footer.next a[href] { +.tutorial-footer .next a[href] { font-weight: 300; } li { margin-top: 5px; } + +.blurbs { + padding-left: 0; + list-style-type: none; + margin-top: 30px; +} + +.blurbs > li { + width: 45%; + float: left; + min-height: 5em; +} + +.blurbs > li.even { + clear: both; + margin-left: 3%; + margin-right: 6%; +} + +.blurbs strong { + font-weight: inherit; + font-size: 110%; +} + +.blurbs em { + display: block; + margin-bottom: 1em; + font-size: 80%; + font-style: normal; +} + +@media (max-width: 620px) { + .blurbs > li { + float: none; + width: 100%; + min-height: 3em; + } + + .blurbs > li.even { + margin-left: 0; + margin-right: 0; + } +} + +.act strong { + font-weight: bold; + font-size: 120%; +} + +.act strong code { + font-size: 110%; +} + +.resources > li { + margin-bottom: 3.5em; +} + +.resources li ul, .resources li ol { + margin-top: 1.5em; + padding-left: 25px; +} + +.splash { + text-align: center; + margin-left: -10%; + white-space: nowrap; +} + +.splash pre { + display: inline-block; +} + +.splash pre.left { + text-align: right; + color: inherit; + background-color: transparent; +} + +.splash pre.right { + text-align: left; +} + +@media (max-width: 700px) { + .splash { + margin-left: 0; + } + + .splash pre.left { + display: none; + } +} + +a[id] { + display: block; + position: relative; + top: -25px; +} + +h4 { + font-weight: normal; + margin-top: 3em; + letter-spacing: -1px; + color: #888; + padding-top: 1em; + white-space: nowrap; +} + +h4 em { + color: #555; + font-style: normal; + font-weight: bold; +} + +.api ul.contents { + -webkit-columns: 300px 2; + -moz-columns: 300px 2; + columns: 300px 2; +} + +.api ul.contents > li { + -webkit-column-break-inside: avoid; + page-break-inside: avoid; + break-inside: avoid; +} + +.main h3 { + margin-top: 4em; +} + +h3.contents { + margin-top: 2em; +} + +.index .main h3 { + margin-top: 2em; +} + +.api .contents ul { + font-size: 75%; +} + +.api .contents > li { + margin-top: 0; + padding-bottom: 1em; +} diff --git a/doc/demo/101-special-values.md b/doc/demo/101-special-values.md index 7d35c13..c98f3e8 100644 --- a/doc/demo/101-special-values.md +++ b/doc/demo/101-special-values.md @@ -1,122 +1,165 @@ ## 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. +*default* values — for example, `Enum::Invalid` is *invalid*, and the +first valid constant is *default*. With Better Enums, you can get the compiler +to enforce the convention. At the end of this demo, we will have defined +functions and templates that allow us to write: ---- +~~~comment +Channel channel = default_; +Channel channel = invalid; + +void do_something(Channel channel); + +do_something(default_); +do_something(invalid); +~~~ + +The compiler will compute default and invalid values automatically, but the +programmer will also be able to override the choice. Obviously, the syntax above +is very legible and maintainable — the intent is clear and your code base +will respond automatically to changes in enum definitions. + +$internal_toc + +### Invalid values + +Let's start by defining the invalid values. #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: +Perhaps the convention is that the invalid value is usually called `Invalid`, +but not for all enums. We will encode that using a template function. The +unspecialized version will encode the default policy: template <typename Enum> - constexpr Enum find_invalid() { return Enum::Invalid; } + constexpr Enum invalid_impl() { return Enum::Invalid; } + +A macro allows us to override the invalid value by specializing the template: #define OVERRIDE_INVALID(Enum, Value) \ template<> \ - constexpr Enum find_invalid() { return Enum::Value; } + constexpr Enum invalid_impl<Enum>() { return Enum::Value; } -Now, you can declare enums like these: +Now, we can declare enums like these: ENUM(Channel, int, Red, Green, Blue, Invalid) + // Invalid is the invalid value by default ENUM(Compression, int, Undefined, None, Huffman) - OVERRIDE_INVALID(Compression, Undefined) + OVERRIDE_INVALID(Compression, Undefined) and use them: - static_assert(find_invalid<Channel>() == +Channel::Invalid, ""); - static_assert(find_invalid<Compression>() == +Compression::Undefined, ""); + static_assert(invalid_impl<Channel>() == +Channel::Invalid, ""); + static_assert(invalid_impl<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! +if you try to call `invalid_impl<>()` on them — as you probably should! -### Default +### Default values -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. +Perhaps here the convention is the first value that is not invalid is default, +unless, again, overridden by the programmer. This can be encoded using only a +slightly more complex template function for the general case: - template - constexpr Enum find_default() + template <typename Enum> + constexpr Enum default_impl() { return - Enum::_size < 2 ? + Enum::_size < 2 ? throw std::logic_error("enum has no valid constants") : - Enum::_values()[0] == find_invalid() ? + Enum::_values()[0] == invalid_impl() ? Enum::_values()[1] : - Enum::_values()[0]; + Enum::_values()[0]; } - #define OVERRIDE_DEFAULT(Enum, Value) \ - static_assert(Enum::Value != Enum::Invalid, \ - #Enum ": default cannot equal invalid"); \ +The above code gives us the first value if it is not invalid, otherwise the +second value. + +The companion macro for overriding the choice of default value is almost the +same as it was for invalid. The difference is that we do an extra sanity check +to make sure the programmer doesn't declare the invalid value to be the default. +If the sanity check fails, we produce a nice error message. Again, we are +assuming that this is dictated by policy. + + #define OVERRIDE_DEFAULT(Enum, Value) \ + static_assert(Enum::Value != Enum::Invalid, \ + #Enum ": default cannot equal invalid"); \ template<> \ - constexpr Enum find_default() { return Enum::Value; } + constexpr Enum default_impl<Enum>() { return Enum::Value; } -Usage: +And, as before, the usage: - static_assert(find_default() == +Channel::Red, ""); - static_assert(find_default() == +Compression::None, ""); + static_assert(default_impl<Channel>() == +Channel::Red, ""); + static_assert(default_impl<Compression>() == +Compression::None, ""); And, if you do - ENUM(Answer, int, Yes, No, Invalid) - // OVERRIDE_DEFAULT(Answer, Invalid) + 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: +At this point, our policy is encoded by the ugly-looking functions +`invalid_impl` and `default_impl`. We want a nicer syntax. The main reason we +don't just use these functions directly is that the compiler wouldn't infer +their template arguments from the context. For example, we would have to write +things like - template - struct assert_enum { - using check = typename Enum::_enumerated; - using type = Enum; +~~~comment +Channel channel = invalid_impl(); +~~~ + +which is unfortunate, because it results in repetition. + +In this section, we introduce two global objects called `invalid` and `default_` +that will implicitly convert to any Better Enum type, and provide the invalid +or default value, respectively, when they do so. They will act as new +"keywords". + + struct invalid_t { + template <typename To> + constexpr operator To() const { return invalid_impl(); } }; - struct invalid_t { - template - constexpr operator To() const { return find_invalid(); } - - template - constexpr To convert() const { return find_invalid(); } + struct default_t { + template <typename To> + constexpr operator To() const { return default_impl(); } }; - struct default_t { - template - constexpr operator To() const { return find_default(); } - }; + constexpr invalid_t invalid{}; + constexpr default_t default_{}; - constexpr invalid_t invalid{}; - constexpr default_t default_{}; +As you can see, both of these provide the families of implicit conversions that +we need. Now, we can test: - static_assert(+Channel::Invalid == invalid, ""); - static_assert(+Compression::Undefined == invalid, ""); + static_assert(+Channel::Invalid == invalid, ""); + static_assert(+Compression::Undefined == invalid, ""); - static_assert(+Channel::Red == default_, ""); - static_assert(+Compression::None == default_, ""); + static_assert(+Channel::Red == default_, ""); + static_assert(+Compression::None == default_, ""); -We can now have nice code such as this: +Finally, we can have nice code such as this: + + void dump(Channel channel) + { + std::cout << channel._to_string() << std::endl; + } int main() { - Channel channel = default_; - std::cout << channel._to_string() << std::endl; + dump(invalid); + + Channel channel = default_; + dump(channel); return 0; } @@ -126,3 +169,6 @@ We can now have nice code such as this: 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! + +%% description = Encoding project policies for static enforcement using Better +Enums. diff --git a/doc/demo/102-bitset.md b/doc/demo/102-bitset.md index 1a01a6a..2f67918 100644 --- a/doc/demo/102-bitset.md +++ b/doc/demo/102-bitset.md @@ -1,6 +1,6 @@ ## Bit sets -If you want to use `std::bitset` or a similar library to use enums as keys into +If you want to use `std::bitset` or a similar library to have enums be 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. @@ -10,41 +10,60 @@ increasing order. We simply need to find the maximum value of any given enum type. #include - #include + #include + #include - template - constexpr Enum max_loop(Enum accumulator, size_t index) + template <typename Enum> + constexpr Enum max_loop(Enum accumulator, size_t index) { return - index >= Enum::_size ? accumulator : + index >= Enum::_size ? accumulator : Enum::_values()[index] > accumulator ? max_loop(Enum::_values()[index], index + 1) : - max_loop(accumulator, index + 1); + max_loop(accumulator, index + 1); } - template - constexpr Enum max() + template <typename Enum> + constexpr Enum max() { - return max_loop(Enum::_values()[0], 1); + return max_loop(Enum::_values()[0], 1); } And use that to declare a bit set template: - template - using EnumSet = std::bitset()._to_integral() + 1>; + template <typename Enum> + using EnumSet = std::bitset<max()._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 -`. +Now, we can have bit sets that are wide enough to hold whatever range we +declared. We just declare enums, and the numeric values of their constants will +be bit indices. The rest is straightforward. - ENUM(Channel, int, Red, Green, Blue) - ENUM(Depth, int, TrueColor = 1, HighColor = 0) + ENUM(EFLAGS, int, + Carry, Parity = 2, Adjust = 4, Zero, Sign, Trap, Interrupt, Direction, + Overflow, NestedTask = 14, Resume = 16, V8086, AlignmentCheck, + CPUIDPresent = 21) int main() { - EnumSet channels; - EnumSet depths; + EnumSet<EFLAGS> eflags = 1 << EFLAGS::Carry | 1 << EFLAGS::Zero; + + if (eflags.test(EFLAGS::Carry)) + eflags.set(EFLAGS::Trap); + + std::cout << eflags << std::endl; return 0; } + +--- + +If we want bit sets of fixed known width instead, we can use the code above to +check that we haven't declared any bit indices out of range: + +~~~comment +static_assert(max()._to_integral() < 32, + "some bit indices are out of range"); +~~~ + +%% description = Finding the maximum value of a Better Enum constant for use in +declaring bit set types. diff --git a/doc/demo/103-quine.md b/doc/demo/103-quine.md index cf39828..27e26d5 100644 --- a/doc/demo/103-quine.md +++ b/doc/demo/103-quine.md @@ -1,117 +1,173 @@ ## 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. +Let's make a Better Enum assemble its own definition in memory. It won't be +literally as defined, since we will lose the exact initializer expressions, but +we will be able to preserve the numeric values. We will reserve the memory +buffer for the definition 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. +Ok, so it's not really a quine, because we won't be writing all the code needed +to generate the definition to the buffer as well. And, there are better ways to +dump the definition than shown here. You could simply define a macro that +expands to an `ENUM` declaration and also stringizes it. + +But that's not the point here. The point of this page is to show some of the +reflective capabilities of Better Enums, so you can adapt them for cases where a +macro is not sufficient :) + +$internal_toc + +--- #include #include #include - #ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING - #define BETTER_ENUMS_CONSTEXPR_TO_STRING +--- + +First, we will need +[full compile-time reflection](${prefix}OptInFeatures.html#CompileTimeNameTrimming), +since we will be calling `_to_string`. Let's make sure it's enabled by defining +`BETTER_ENUMS_CONSTEXPR_TO_STRING` before including `enum.h`: + + #ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING + #define BETTER_ENUMS_CONSTEXPR_TO_STRING #endif - #include + #include - #define HIGH_COLOR 0 +Now, let's declare some enums to dump later: - ENUM(Channel, int, Red, Green, Blue) - ENUM(Depth, int, TrueColor = 1, HighColor = HIGH_COLOR) + ENUM(Channel, int, Red, Green, Blue) + ENUM(Depth, int, TrueColor = 1, HighColor = 0) -First, we need to be able to get the length of each definition above. We will + + +### Computing the size of the buffer + +First, we need to be able to get the length of each declaration 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: +is followed as above. - constexpr size_t value_length(int n, int bound = 10, size_t digits = 1) +First, let's get the lengths of basic components: + + // Returns the length of the string representation of the number n + 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); + n < bound ? digits : value_length(n, bound * 10, digits + 1); } - constexpr size_t string_length(const char *s, size_t index = 0) + // Returns the length of s + constexpr size_t string_length(const char *s, size_t index = 0) { - return s[index] == '\0' ? index : string_length(s, index + 1); + return s[index] == '\0' ? index : string_length(s, index + 1); } - template - constexpr size_t constants_length(size_t index = 0, size_t accumulator = 0) +Now, the length of the constant declaration. Here is where we lose information +about initializers. We are going to format the constant declarations like this: + +~~~comment +Red = 0, Green = 1, Blue = 2 +TrueColor = 1, HighColor = 0 +~~~ + +This is because Better Enums doesn't provide a way to know what the exact +initializer was or whether there even was one — just the numeric value of +each constant. If we were trying to be clever, we could avoid formatting +initializers for sequential values, but I won't go through this exercise here. + + // Returns the length of the constants portion of the declaration of Enum, + // as described above. + template <typename Enum> + constexpr size_t constants_length(size_t index = 0, size_t accumulator = 0) { return - index >= Enum::_size ? accumulator : - + 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())); + Enum::_values()[index]._to_integral())); } - template - constexpr size_t declaration_length() +Finally, we can combine these to get the length of the formatted declaration of +the whole enum: + + // Returns the length of the whole declaration of Enum, assuming the + // underlying type is int, and the constants are initialized as assumed by + // constants_length() above. + template <typename Enum> + constexpr size_t declaration_length() { return - string_length("ENUM(") + string_length("ENUM(") + string_length(Enum::_name()) + string_length(", int") + constants_length() - + string_length(")"); + + string_length(")"); } -Now, we can declare: - char channel_definition[declaration_length() + 1]; - char depth_definition[declaration_length() + 1]; -And finally, the formatting function: +### Formatting the enums - template - size_t format(char *buffer) +Now, we can declare the buffers. The memory will be reserved at load time by the +binary's loader. The extra one byte in each buffer is for the null terminator. + + char channel_definition[declaration_length() + 1]; + char depth_definition[declaration_length() + 1]; + +Let's also create the formatting function. This is executed at run time, but we +will be giving it pointers to our statically-allocated buffers. It will format +the enum declaration and then return the number of bytes it wrote to the buffer, +so that we can do a sanity check on it. + + template <typename Enum> + size_t format(char *buffer) { size_t offset = 0; - offset += std::sprintf(buffer, "ENUM(%s, int", Enum::_name()); + offset += std::sprintf(buffer, "ENUM(%s, int", Enum::_name()); - for (Enum value : Enum::_values()) { + for (Enum value : Enum::_values()) { offset += std::sprintf(buffer + offset, - ", %s = %i", - value._to_string(), value._to_integral()); + ", %s = %i", + value._to_string(), value._to_integral()); } - offset += std::sprintf(buffer + offset, ")"); + offset += std::sprintf(buffer + offset, ")"); - return offset; + return offset; } + + +### Checking our work + +Now, we can write and run this code. + int main() { - size_t channel_length = format(channel_definition); + size_t channel_length = format(channel_definition); assert(channel_length + 1 == sizeof(channel_definition)); - size_t depth_length = format(depth_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; + std::cout << channel_definition << std::endl; + std::cout << depth_definition << std::endl; return 0; } -This outputs: +It prints: ~~~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. +%% description = Contrived example that shows static memory allocation. diff --git a/doc/docs.py b/doc/docs.py index 35f7879..dae8fcf 100755 --- a/doc/docs.py +++ b/doc/docs.py @@ -16,6 +16,8 @@ CXX_EXTENSION = "cc" templates = {} +generated = [] + def load_templates(): listing = os.listdir(TEMPLATE_DIRECTORY) @@ -45,7 +47,7 @@ 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) + path = os.path.join(OUTPUT_DIRECTORY, relative_path) directory = os.path.dirname(path) if not os.path.lexists(directory): @@ -54,7 +56,7 @@ def path_to_output(relative_path): return path def remove_output_directory(): - path = os.path.join(OUTPUT_DIRECTORY, templates["version"]) + path = OUTPUT_DIRECTORY if os.path.lexists(path): shutil.rmtree(path) @@ -66,9 +68,13 @@ def compose_page(relative_path, definitions): if html_file == "index.html": canonical = templates["location"] definitions["title"] = \ - definitions["project"] + " - " + definitions["title"] + definitions["project"] + " - " + definitions["title"] else: - canonical = os.path.join(templates["location"], "current", html_file) + canonical = os.path.join(templates["location"], html_file) + definitions["title"] = \ + definitions["title"] + " - " + definitions["project"] + + generated.append(canonical) prefix = re.sub("[^/]+", r"..", os.path.split(relative_path)[0]) if len(prefix) > 0: @@ -77,6 +83,9 @@ def compose_page(relative_path, definitions): definitions["canonical"] = canonical definitions["prefix"] = prefix + if "class" not in definitions: + definitions["class"] = "" + text = templates["page"] text = scrub_comments(text) @@ -84,13 +93,12 @@ def compose_page(relative_path, definitions): text = apply_template(text, definitions) text = scrub_comments(text) - text = "\n\n" + 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) +def write_file(relative_path, text): + path = path_to_output(relative_path) stream = open(path, "w") try: @@ -98,6 +106,9 @@ def write_page(relative_path, text): finally: stream.close() +def write_page(relative_path, text): + write_file(path_to_html(relative_path), text) + def copy_static_file(relative_path): output_path = path_to_output(relative_path) shutil.copy(relative_path, output_path) @@ -127,11 +138,8 @@ def process_threaded(directory): 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") + transformed_title = transform.camel_case(definitions["title"]) + html_file = os.path.join(directory, transformed_title + ".html") source_file = \ os.path.splitext(os.path.basename(file))[0] + "." + CXX_EXTENSION @@ -166,6 +174,21 @@ def process_threaded(directory): templates[directory + "_toc"] = text +def generate_sitemap(): + text = "" + + text += "\n" + text += "\n" + + for url in generated: + text += " \n" + text += " %s\n" % url + text += " \n" + + text += "\n" + + write_file("sitemap.xml", text) + def main(): load_templates() @@ -180,5 +203,7 @@ def main(): copy_static_file("better-enums.css") + generate_sitemap() + if __name__ == "__main__": main() diff --git a/doc/index.md b/doc/index.md index 2189f61..4c8262b 100644 --- a/doc/index.md +++ b/doc/index.md @@ -1,125 +1,199 @@ -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. +Better Enums is a single header file that causes your compiler to generate +*reflective* enum types. This makes it easy to translate between enums and +strings, and much more. -$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! +Here's how to use a Better Enum: -
-
-

$cxx11

+
+
enable
 
-    
#include <iostream>
-#include <enum.h>
+declare
 
-ENUM(Channel, int,
-     Red = 1, Green, Blue)
 
-int main()
-{
-  Channel c = Channel::Red;
-  std::cout << c._to_string();
+parse
+print
 
-  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;
-  }
+count
+iterate
+
+
+
+switch
+
+
+
+
+
+
+safe cast
+
+
+at compile time
+
#include <enum.h>
+
+ENUM(Channel, int, Red = 1, Green, Blue)
+
+
+Channel     c = Channel::_from_string("Red");
+const char  *s = c._to_string();
+
+
+size_t      n = Channel::_size;
+for (Channel c : Channel::_values())
+    run_some_function(c);
+
+
+switch (c) {
+    case Channel::Red:    // ...
+    case Channel::Green:  // ...
+    case Channel::Blue:   // ...
 }
 
-constexpr Channel c =
-  Channel::_from_string("Blue");
-
-
-

$cxx98

-
#include <iostream>
-#include <enum.h>
 
-ENUM(Channel, int,
-     Red = 1, Green, Blue)
+Channel     c = Channel::_from_integral(3);
 
-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;
-  }
-}
-
- -
+constexpr Channel c = Channel::_from_string("Blue");
-To install, simply download enum.h and add it to your project. +### What do you get? -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. +
    +
  • + Uniform interface for $cxx98 and $cxx11 + Scoped, sized, reflective enums for $cxx98. +
  • +
  • + Compile-time reflection + + Have the compiler do additional enum processing using your own + templates or constexpr functions. + +
  • ---- +
  • + Non-contiguous sequences + + Iteration and count much easier to maintain than with an extra "count" + constant and making assumptions. + +
  • +
  • + Plays nice with switch + + Use a Better Enum like a built-in enum, and still have the + compiler do case checking. + +
  • -Better Enums is under active development and will always be supported. Follow -the project on [GitHub]($repo) for updates. +
  • + Unobtrusive syntax + + No ugly macros. Use initializers just like with built-in + enums. Generated members have underscores to avoid conflicts + with your constant names. + +
  • +
  • + Don't repeat yourself + + No more unmaintanable maps or switch statements for + converting enums to strings. + +
  • ---- +
  • + No build-time generator needed + + Just include enum.h. It's a metaprogram executed by your + compiler. + +
  • +
  • + Fast compilation + + Much less impact on build time than even just including + iostream. + +
  • -
    -
    -

    Tutorials

    -
      - $tutorial_toc -
    -
    - -
    -

    Advanced demos

    -
      - $demo_toc -
    -
    -
    - - +
  • + No external dependencies + + Uses standard $cxx and supported on major compilers. Installation is + simple — just download enum.h. + +
  • +
  • + Free and open source + + Released under the BSD license for use in any project, free or commercial. + +
  • +
- +### It's what built-in enums ought to do. - +The library notionally extends $cxx, adding oft-needed features. - +
    +
  • + Download enum.h + + Current version: $version
    + To install, just add the file to your project. +
    +
  • +
  • + Visit on GitHub + + Follow development, report issues, and let me know if you find this + library useful! + +
  • +
- +
+ +### Resources + + + +
%% title = Clean reflective enums for C++ + +%% description = Reflective enums for C++ with clean syntax, in a header-only +library. Can be converted to strings, iterated, counted, and used for +metaprogramming. Free under the BSD license. + +%% class = index diff --git a/doc/performance.md b/doc/performance.md deleted file mode 100644 index e23314a..0000000 --- a/doc/performance.md +++ /dev/null @@ -1,7 +0,0 @@ -## 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/template/demo.tmpl b/doc/template/demo.tmpl index 1cd1edc..f87df8a 100644 --- a/doc/template/demo.tmpl +++ b/doc/template/demo.tmpl @@ -1,6 +1,13 @@

- The code on this page is an advanced demo of Better Enums. You can - download it and try it out. + This page is an advanced demo showing the kind of compile-time code you can + write on top 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 index 46e6804..76cc260 100644 --- a/doc/template/download.tmpl +++ b/doc/template/download.tmpl @@ -1,2 +1,2 @@ -href="https://raw.githubusercontent.com/aantron/better-enums/$ref/enum.h" +href="https://raw.githubusercontent.com/aantron/better-enums/$version/enum.h" download \ No newline at end of file diff --git a/doc/template/footer.tmpl b/doc/template/footer.tmpl index d7b0d88..2d96811 100644 --- a/doc/template/footer.tmpl +++ b/doc/template/footer.tmpl @@ -1,11 +1,14 @@ - +
+
- Copyright © 2015 Anton Bachin. Released under the BSD 2-clause license. - See - LICENSE. -
- This page is part of the documentation for Better Enums $version. +
+ 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 index 0e6d468..1630395 100644 --- a/doc/template/header.tmpl +++ b/doc/template/header.tmpl @@ -6,7 +6,7 @@ $title - + @@ -14,30 +14,30 @@ - +
 
-
{}
+
+
{}
-

Better Enums

-

Fast, intuitive enums for $cxx

-

Open-source under the BSD license

+

Better Enums

+

Reflective compile-time enums for $cxx

+

Open-source under the BSD license

+
-
+
+
diff --git a/doc/template/next.tmpl b/doc/template/next.tmpl index 20d640c..67221d4 100644 --- a/doc/template/next.tmpl +++ b/doc/template/next.tmpl @@ -4,6 +4,7 @@

- Or, return to the home page. + Or, return to the + tutorial index.

diff --git a/doc/template/ref.tmpl b/doc/template/ref.tmpl deleted file mode 100644 index 8b25206..0000000 --- a/doc/template/ref.tmpl +++ /dev/null @@ -1 +0,0 @@ -master \ No newline at end of file diff --git a/doc/template/version.tmpl b/doc/template/version.tmpl index 8b25206..899f24f 100644 --- a/doc/template/version.tmpl +++ b/doc/template/version.tmpl @@ -1 +1 @@ -master \ No newline at end of file +0.9.0 \ No newline at end of file diff --git a/doc/transform.py b/doc/transform.py index 4cad24a..63ef2a7 100755 --- a/doc/transform.py +++ b/doc/transform.py @@ -9,7 +9,7 @@ import mistune import re import sys -parser = argparse.ArgumentParser(description = "Translate markdown tutorials.", +parser = argparse.ArgumentParser(description = "Translate markdown pages.", epilog = "At least one output file must be specified") parser.add_argument("--o-html", metavar = "HTML", dest = "html_file", help = "HTML output file name") @@ -46,17 +46,44 @@ def pretty_print(text, prefix, start_with_prefix = True): return result +def camel_case(text): + components = re.split("[ -]+", text) + components = map(lambda s: s.capitalize(), components) + result = "".join(components) + result = filter(lambda c: c not in ",!:-$()", result) + return result + class HtmlRenderer(mistune.Renderer): def __init__(self): super(HtmlRenderer, self).__init__() self._definitions = {} + self._table_of_contents = [] + self._second_level = None 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) + if level == 3: + anchor_name = camel_case(text) + prefix = "" % anchor_name + + self._second_level = [] + self._table_of_contents.append( + (anchor_name, text, self._second_level)) + + elif level == 4: + anchor_text = re.search("(.+)", text).group(1) + anchor_name = camel_case(anchor_text) + prefix = "" % anchor_name + + self._second_level.append((anchor_name, anchor_text)) + + else: + prefix = "" + + return prefix + super(HtmlRenderer, self).header(text, level, raw) def paragraph(self, text): if text.startswith("%%"): @@ -76,7 +103,6 @@ class HtmlRenderer(mistune.Renderer): 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 = "
"
@@ -86,6 +112,26 @@ class HtmlRenderer(mistune.Renderer):
         return start_tag + replaced + "
" def definitions(self): + toc_text = "" + toc_text += "

Contents

" + toc_text += "
    " + + for entry in self._table_of_contents: + anchor, title, second_level = entry + toc_text += "
  • %s" % (anchor, title) + + if len(second_level) > 0: + toc_text += "
      " + for entry in second_level: + toc_text += "
    • %s
    • " % entry + toc_text += "
    " + + toc_text += "
  • " + + toc_text += "
" + + self._definitions["internal_toc"] = toc_text + return self._definitions def to_html(text): @@ -95,6 +141,13 @@ def to_html(text): definitions["body"] = html return definitions +def clean_text(text): + text = re.sub("<(?P[^> ]+)[^>]*>(.*?)", "\g<2>", text) + text = re.sub("<(?P[^> ]+)[^>]*>(.*?)", "\g<2>", text) + text = re.sub("—", "-", text) + text = re.sub("\$cxx", "C++", text) + return text + class CxxRenderer(mistune.Renderer): def __init__(self): super(CxxRenderer, self).__init__() @@ -109,18 +162,25 @@ class CxxRenderer(mistune.Renderer): if text.startswith("%%"): return "" + if text == "$internal_toc": + return "" + self._not_in_list() + text = clean_text(text) return self._join_paragraph() + pretty_print(text, "// ") def codespan(self, text): return text + def link(self, link, title, content): + return content + 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) + pretty_print(clean_text(text), "// ", False) def block_code(self, code, lang): self._not_in_list() @@ -138,7 +198,7 @@ class CxxRenderer(mistune.Renderer): def hrule(self): self._not_in_list() self._not_paragraph() - return "" + return "\n" def footnote_ref(self, key, index): return "" @@ -185,7 +245,7 @@ def main(md_file, html_file, cxx_file): 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 + source = "// This file was generated automatically.\n\n" + source open(cxx_file, "w").write(source) if __name__ == "__main__": diff --git a/doc/tutorial/1-hello-world.md b/doc/tutorial/1-hello-world.md index fe639d1..0cb72b5 100644 --- a/doc/tutorial/1-hello-world.md +++ b/doc/tutorial/1-hello-world.md @@ -1,11 +1,11 @@ ## Hello, World! -Download enum.h, then build this program with it: +Download enum.h, then build this program with it: #include #include "enum.h" - ENUM(Word, int, Hello, World) + ENUM(Word, int, Hello, World) int main() { @@ -19,3 +19,5 @@ Download enum.h, then build this program with it: Run it, and you should see the output "Hello, World!" Congratulations, you have just created your first Better Enum! + +%% description = Introductory Better Enums tutorial. diff --git a/doc/tutorial/2-conversions.md b/doc/tutorial/2-conversions.md index f66d4d6..42d4d41 100644 --- a/doc/tutorial/2-conversions.md +++ b/doc/tutorial/2-conversions.md @@ -7,7 +7,7 @@ Let's begin by including `enum.h` and declaring our enum: #include - ENUM(Channel, int, Cyan = 1, Magenta, Yellow, Black) + ENUM(Channel, int, Cyan = 1, Magenta, Yellow, Black) We now have an `int`-sized enum with four constants. @@ -15,6 +15,8 @@ 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. +$internal_toc + ### Strings There are three functions: @@ -117,12 +119,6 @@ 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, @@ -144,14 +140,13 @@ There is one unfortunate wrinkle. You cannot convert a literal constant such as 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! +reference has a +[full explanation](${prefix}ApiReference.html#HelperFunctionsAndTypes). --- std::cout << std::endl; return 0; } + +%% description = Walkthrough of Better Enums conversion functions. diff --git a/doc/tutorial/3-iterate.md b/doc/tutorial/3-iterate.md index 7d689bc..3e3d846 100644 --- a/doc/tutorial/3-iterate.md +++ b/doc/tutorial/3-iterate.md @@ -32,16 +32,18 @@ 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 (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; + for (const char *name : Channel::_names()) + std::cout << name << " "; + std::cout << std::endl; ~~~ --- return 0; } + +%% description = Iterating over all Better Enums constants. diff --git a/doc/tutorial/4-switch.md b/doc/tutorial/4-switch.md index 8175423..2d0347a 100644 --- a/doc/tutorial/4-switch.md +++ b/doc/tutorial/4-switch.md @@ -26,3 +26,5 @@ you a warning — try it! std::cout << n << std::endl; return 0; } + +%% description = Usage in switch statements. diff --git a/doc/tutorial/5-safety.md b/doc/tutorial/5-safety.md index ab003d3..cd96b18 100644 --- a/doc/tutorial/5-safety.md +++ b/doc/tutorial/5-safety.md @@ -3,6 +3,8 @@ This tutorial shows some of the safety features of Better Enums: scope, how to control conversions, and the lack of a default constructor. +$internal_toc + ### Scope You have probably noticed by now that Better Enums are scoped: when you declare @@ -10,7 +12,7 @@ You have probably noticed by now that Better Enums are scoped: when you declare #include #include - ENUM(Channel, int, Red = 1, Green, Blue) + 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 @@ -18,7 +20,7 @@ $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) + ENUM(Node, char, Red, Black) and everything will work as expected. @@ -42,22 +44,8 @@ will not compile: 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. +The reason this is not enabled by default is explained in the reference page on +[strict conversions](${prefix}OptInFeatures.html#StrictConversions). ### Default constructor @@ -73,29 +61,19 @@ Better Enums don't have a default constructor, for three reasons. So, if you uncomment this code, the file won't compile: - // Channel channel; +~~~comment + 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. +This may be too strict, and I may relax it in the future. In the meantime, the +solution sketched in the [special values demo](${prefix}demo/SpecialValues.html) +can replace default constructors for some purposes, and in a more flexible way. +I may eventually have the default constructor calling a template function like +the one in that demo. --- + return 0; } + +%% description = Type safety features and limitations. diff --git a/doc/tutorial/6-representation.md b/doc/tutorial/6-representation.md index 6d0583e..c5ea49d 100644 --- a/doc/tutorial/6-representation.md +++ b/doc/tutorial/6-representation.md @@ -1,4 +1,4 @@ -## Representation and alignment +## Representation 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. @@ -7,8 +7,8 @@ will declare a more unusual enum than the ones we have seen. #include #include - ENUM(ContentType, short, - CompressedVideo = 5, PCM = 8, Subtitles = 17, Comment = 44) + 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: @@ -25,22 +25,21 @@ Here is what we have. int main() { - assert(sizeof(ContentType) == 2); + 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: +`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}; + Header header = {ContentType::PCM, 0, 0}; - assert(sizeof(header) == 8); - assert((size_t)&header.flags - (size_t)&header.type == 2); + 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`: +available as the member type `::_integral`: ContentType::_integral untrusted_value = 44; @@ -80,3 +79,5 @@ types containg enums. The enums will behave as expected. 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. + +%% description = Underlying representation. diff --git a/doc/tutorial/7-constexpr.md b/doc/tutorial/7-constexpr.md index 306db30..c913900 100644 --- a/doc/tutorial/7-constexpr.md +++ b/doc/tutorial/7-constexpr.md @@ -2,27 +2,33 @@ 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. +examples in *this* tutorial aren't very useful, but look at the +[demos](${prefix}index.html#CompileTimeDemos) at the bottom of the main page to +get an idea of what can be done. Here, you will see the basics. #include + // The reason for this is explained below. #ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING #define BETTER_ENUMS_CONSTEXPR_TO_STRING #endif #include - ENUM(Channel, int, Red = 1, Green = 2, Blue = 3) + ENUM(Channel, int, Red = 1, Green = 2, Blue = 3) - constexpr Channel channel = Channel::Green; + constexpr Channel channel = Channel::_from_integral(2); 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: +All of the above are computed during compilation. The reason for the macro +definition at the top of the file is explained on the +[opt-in features page](${prefix}OptInFeatures.html#CompileTimeNameTrimming). +Basically, it makes `_to_string` `constexpr`, but slows down compilation. + +You can also do things such as: constexpr size_t length(const char *s, size_t index = 0) { @@ -41,3 +47,5 @@ things such as: Which prints "5", the length of "Green". That 5 was also computed during compilation. + +%% description = Introduction to compile-time conversions. diff --git a/enum.h b/enum.h index d0a481c..9b02855 100644 --- a/enum.h +++ b/enum.h @@ -1,8 +1,6 @@ // 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_ diff --git a/example/1-hello-world.cc b/example/1-hello-world.cc index cf40093..67d14dd 100644 --- a/example/1-hello-world.cc +++ b/example/1-hello-world.cc @@ -1,8 +1,8 @@ -// This file was generated automatically +// This file was generated automatically. // Hello, World! // -// Download enum.h, then build this program with it: +// Download enum.h, then build this program with it: #include #include "enum.h" diff --git a/example/101-special-values.cc b/example/101-special-values.cc index 9a2adf9..dc154a0 100644 --- a/example/101-special-values.cc +++ b/example/101-special-values.cc @@ -1,73 +1,100 @@ -// This file was generated automatically +// 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. +// Suppose your project has a convention where each enum has special invalid and +// default values - for example, Enum::Invalid is invalid, and the first valid +// constant is default. With Better Enums, you can get the compiler to enforce +// the convention. At the end of this demo, we will have defined functions and +// templates that allow us to write: +// +// Channel channel = default_; +// Channel channel = invalid; +// +// void do_something(Channel channel); +// +// do_something(default_); +// do_something(invalid); +// +// The compiler will compute default and invalid values automatically, but the +// programmer will also be able to override the choice. Obviously, the syntax +// above is very legible and maintainable - the intent is clear and your code +// base will respond automatically to changes in enum definitions. +// +// Invalid values +// +// Let's start by defining the invalid values. #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: +// Perhaps the convention is that the invalid value is usually called Invalid, +// but not for all enums. We will encode that using a template function. The +// unspecialized version will encode the default policy: template -constexpr Enum find_invalid() { return Enum::Invalid; } +constexpr Enum invalid_impl() { return Enum::Invalid; } + +// A macro allows us to override the invalid value by specializing the template: #define OVERRIDE_INVALID(Enum, Value) \ template<> \ -constexpr Enum find_invalid() { return Enum::Value; } +constexpr Enum invalid_impl() { return Enum::Value; } -// Now, you can declare enums like these: +// Now, we can declare enums like these: ENUM(Channel, int, Red, Green, Blue, Invalid) +// Invalid is the invalid value by default 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, ""); +static_assert(invalid_impl() == +Channel::Invalid, ""); +static_assert(invalid_impl() == +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! +// if you try to call invalid_impl<>() on them - as you probably should! // -// Default +// Default values // -// 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. +// Perhaps here the convention is the first value that is not invalid is +// default, unless, again, overridden by the programmer. This can be encoded +// using only a slightly more complex template function for the general case: template -constexpr Enum find_default() +constexpr Enum default_impl() { return Enum::_size < 2 ? throw std::logic_error("enum has no valid constants") : - Enum::_values()[0] == find_invalid() ? + Enum::_values()[0] == invalid_impl() ? Enum::_values()[1] : Enum::_values()[0]; } +// The above code gives us the first value if it is not invalid, otherwise the +// second value. +// +// The companion macro for overriding the choice of default value is almost the +// same as it was for invalid. The difference is that we do an extra sanity +// check to make sure the programmer doesn't declare the invalid value to be the +// default. If the sanity check fails, we produce a nice error message. Again, +// we are assuming that this is dictated by policy. + #define OVERRIDE_DEFAULT(Enum, Value) \ static_assert(Enum::Value != Enum::Invalid, \ #Enum ": default cannot equal invalid"); \ template<> \ -constexpr Enum find_default() { return Enum::Value; } +constexpr Enum default_impl() { return Enum::Value; } -// Usage: +// And, as before, the usage: -static_assert(find_default() == +Channel::Red, ""); -static_assert(find_default() == +Compression::None, ""); +static_assert(default_impl() == +Channel::Red, ""); +static_assert(default_impl() == +Compression::None, ""); // And, if you do @@ -79,48 +106,61 @@ ENUM(Answer, int, Yes, No, 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; -}; +// At this point, our policy is encoded by the ugly-looking functions +// invalid_impl and default_impl. We want a nicer syntax. The main reason we +// don't just use these functions directly is that the compiler wouldn't infer +// their template arguments from the context. For example, we would have to +// write things like +// +// Channel channel = invalid_impl(); +// +// which is unfortunate, because it results in repetition. +// +// In this section, we introduce two global objects called invalid and default_ +// that will implicitly convert to any Better Enum type, and provide the invalid +// or default value, respectively, when they do so. They will act as new +// "keywords". struct invalid_t { template - constexpr operator To() const { return find_invalid(); } - - template - constexpr To convert() const { return find_invalid(); } + constexpr operator To() const { return invalid_impl(); } }; struct default_t { template - constexpr operator To() const { return find_default(); } + constexpr operator To() const { return default_impl(); } }; constexpr invalid_t invalid{}; constexpr default_t default_{}; +// As you can see, both of these provide the families of implicit conversions +// that we need. Now, we can test: + 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: +// Finally, we can have nice code such as this: + +void dump(Channel channel) +{ + std::cout << channel._to_string() << std::endl; +} int main() { + dump(invalid); + Channel channel = default_; - std::cout << channel._to_string() << std::endl; + dump(channel); 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 index 3c39317..6e59a18 100644 --- a/example/102-bitset.cc +++ b/example/102-bitset.cc @@ -1,14 +1,16 @@ -// This file was generated automatically +// 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 +// If you want to use std::bitset or a similar library to have enums be 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 #include template @@ -32,18 +34,30 @@ constexpr Enum max() 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 -. +// Now, we can have bit sets that are wide enough to hold whatever range we +// declared. We just declare enums, and the numeric values of their constants +// will be bit indices. The rest is straightforward. -ENUM(Channel, int, Red, Green, Blue) -ENUM(Depth, int, TrueColor = 1, HighColor = 0) +ENUM(EFLAGS, int, + Carry, Parity = 2, Adjust = 4, Zero, Sign, Trap, Interrupt, Direction, + Overflow, NestedTask = 14, Resume = 16, V8086, AlignmentCheck, + CPUIDPresent = 21) int main() { - EnumSet channels; - EnumSet depths; + EnumSet eflags = 1 << EFLAGS::Carry | 1 << EFLAGS::Zero; + + if (eflags.test(EFLAGS::Carry)) + eflags.set(EFLAGS::Trap); + + std::cout << eflags << std::endl; return 0; } + + +// If we want bit sets of fixed known width instead, we can use the code above +// to check that we haven't declared any bit indices out of range: +// +// static_assert(max()._to_integral() < 32, +// "some bit indices are out of range"); diff --git a/example/103-quine.cc b/example/103-quine.cc index 87aa91a..3cc1db0 100644 --- a/example/103-quine.cc +++ b/example/103-quine.cc @@ -1,53 +1,85 @@ -// This file was generated automatically +// 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. +// Let's make a Better Enum assemble its own definition in memory. It won't be +// literally as defined, since we will lose the exact initializer expressions, +// but we will be able to preserve the numeric values. We will reserve the +// memory buffer for the definition 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. +// Ok, so it's not really a quine, because we won't be writing all the code +// needed to generate the definition to the buffer as well. And, there are +// better ways to dump the definition than shown here. You could simply define a +// macro that expands to an ENUM declaration and also stringizes it. +// +// But that's not the point here. The point of this page 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 + +// First, we will need full compile-time reflection, since we will be calling +// _to_string. Let's make sure it's enabled by defining +// BETTER_ENUMS_CONSTEXPR_TO_STRING before including enum.h: + #ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING #define BETTER_ENUMS_CONSTEXPR_TO_STRING #endif #include -#define HIGH_COLOR 0 +// Now, let's declare some enums to dump later: ENUM(Channel, int, Red, Green, Blue) -ENUM(Depth, int, TrueColor = 1, HighColor = HIGH_COLOR) +ENUM(Depth, int, TrueColor = 1, HighColor = 0) -// 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: + +// Computing the size of the buffer +// +// First, we need to be able to get the length of each declaration above. We +// will assume that the underlying type is always int, and that the spacing +// convention is followed as above. +// +// First, let's get the lengths of basic components: + +// Returns the length of the string representation of the number n 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); } +// Returns the length of s constexpr size_t string_length(const char *s, size_t index = 0) { return s[index] == '\0' ? index : string_length(s, index + 1); } +// Now, the length of the constant declaration. Here is where we lose +// information about initializers. We are going to format the constant +// declarations like this: +// +// Red = 0, Green = 1, Blue = 2 +// TrueColor = 1, HighColor = 0 +// +// This is because Better Enums doesn't provide a way to know what the exact +// initializer was or whether there even was one - just the numeric value of +// each constant. If we were trying to be clever, we could avoid formatting +// initializers for sequential values, but I won't go through this exercise +// here. + +// Returns the length of the constants portion of the declaration of Enum, +// as described above. 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(", ") @@ -57,6 +89,12 @@ constexpr size_t constants_length(size_t index = 0, size_t accumulator = 0) Enum::_values()[index]._to_integral())); } +// Finally, we can combine these to get the length of the formatted declaration +// of the whole enum: + +// Returns the length of the whole declaration of Enum, assuming the +// underlying type is int, and the constants are initialized as assumed by +// constants_length() above. template constexpr size_t declaration_length() { @@ -68,12 +106,21 @@ constexpr size_t declaration_length() + string_length(")"); } -// Now, we can declare: + + +// Formatting the enums +// +// Now, we can declare the buffers. The memory will be reserved at load time by +// the binary's loader. The extra one byte in each buffer is for the null +// terminator. char channel_definition[declaration_length() + 1]; char depth_definition[declaration_length() + 1]; -// And finally, the formatting function: +// Let's also create the formatting function. This is executed at run time, but +// we will be giving it pointers to our statically-allocated buffers. It will +// format the enum declaration and then return the number of bytes it wrote to +// the buffer, so that we can do a sanity check on it. template size_t format(char *buffer) @@ -94,6 +141,12 @@ size_t format(char *buffer) return offset; } + + +// Checking our work +// +// Now, we can write and run this code. + int main() { size_t channel_length = format(channel_definition); @@ -108,10 +161,7 @@ int main() return 0; } -// This outputs: +// It prints: // // 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 index 6368d4e..337980b 100644 --- a/example/2-conversions.cc +++ b/example/2-conversions.cc @@ -1,4 +1,4 @@ -// This file was generated automatically +// This file was generated automatically. // Conversions // @@ -32,14 +32,16 @@ int main() // 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. +// 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 = @@ -50,10 +52,8 @@ int main() else std::cout << maybe_channel->_to_string() << " "; -// This returns an optional value, in the style of boost::optional -// or the proposed std::optional. +// This returns an optional value, in the style of boost::optional or the +// proposed std::optional. // // What that means for the above code is: // @@ -61,7 +61,7 @@ int main() // *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: +// In C++11, you can use auto to avoid writing out the optional type: // // auto maybe_channel = Channel::_from_string_nothrow("Yellow"); // if (!maybe_channel) @@ -85,7 +85,7 @@ int main() // Integers // -// And, it is similar with the representation type int: +// And, it is similar with the representation type int: // // 1. ._to_integral // 2. ::_from_integral @@ -109,22 +109,17 @@ int main() 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, +// 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 @@ -134,8 +129,8 @@ int main() 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! +// The reference has a full explanation. + std::cout << std::endl; return 0; diff --git a/example/3-iterate.cc b/example/3-iterate.cc index 46abe51..96d76b8 100644 --- a/example/3-iterate.cc +++ b/example/3-iterate.cc @@ -1,4 +1,4 @@ -// This file was generated automatically +// This file was generated automatically. // Iteration // @@ -28,15 +28,17 @@ int main() std::cout << std::endl; // will print "Red Green Blue". -// If you are using $cxx11, you can have much nicer syntax: + +// If you are using C++11, you can have much nicer syntax: // -// for (Channel channel : Channel::_values()) -// std::cout << channel._to_integral() << " "; -// std::cout << std::endl; +// 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; +// for (const char *name : Channel::_names()) +// std::cout << name << " "; +// std::cout << std::endl; + return 0; } diff --git a/example/4-switch.cc b/example/4-switch.cc index f45ac30..a59728a 100644 --- a/example/4-switch.cc +++ b/example/4-switch.cc @@ -1,4 +1,4 @@ -// This file was generated automatically +// This file was generated automatically. // Safe switch // @@ -21,7 +21,8 @@ int main() } // If you miss a case or add a redundant one, your compiler should be able to -// give you a warning — try it! +// give you a warning - try it! + std::cout << n << std::endl; return 0; diff --git a/example/5-safety.cc b/example/5-safety.cc index f50d845..31bcd55 100644 --- a/example/5-safety.cc +++ b/example/5-safety.cc @@ -1,4 +1,4 @@ -// This file was generated automatically +// This file was generated automatically. // Scope and safety // @@ -16,10 +16,10 @@ 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 +// Channel, and Red is accessible as Channel::Red. This is no big deal in C++11, +// which has enum class. In C++98, 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) @@ -31,31 +31,19 @@ int main() // Implicit conversion // -// A major complaint in $cxx98 is that enums are implicitly convertible to +// A major complaint in C++98 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 +// Better Enums can be made as safe as enum class in C++11, 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. +// The reason this is not enabled by default is explained in the reference page +// on strict conversions. // // Default constructor // @@ -71,31 +59,15 @@ int main() // 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. +// Channel channel; +// +// This may be too strict, and I may relax it in the future. In the meantime, +// the solution sketched in the special values demo can replace default +// constructors for some purposes, and in a more flexible way. I may eventually +// have the default constructor calling a template function like the one in that +// demo. + + return 0; } diff --git a/example/6-representation.cc b/example/6-representation.cc index fbbb81c..b04b9cf 100644 --- a/example/6-representation.cc +++ b/example/6-representation.cc @@ -1,6 +1,6 @@ -// This file was generated automatically +// This file was generated automatically. -// Representation and alignment +// Representation // // 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. @@ -21,24 +21,25 @@ struct Header { 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: +// 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: + +// 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 the member type ::_integral: ContentType::_integral untrusted_value = 44; @@ -51,17 +52,20 @@ int main() 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 = +// 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/7-constexpr.cc b/example/7-constexpr.cc index 351cb68..00c4ac8 100644 --- a/example/7-constexpr.cc +++ b/example/7-constexpr.cc @@ -1,15 +1,16 @@ -// This file was generated automatically +// 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. +// When used with C++11, 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 look at the demos at the +// bottom of the main page to get an idea of what can be done. Here, you will +// see the basics. #include +// The reason for this is explained below. #ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING #define BETTER_ENUMS_CONSTEXPR_TO_STRING #endif @@ -18,14 +19,17 @@ ENUM(Channel, int, Red = 1, Green = 2, Blue = 3) -constexpr Channel channel = Channel::Green; +constexpr Channel channel = Channel::_from_integral(2); 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: +// All of the above are computed during compilation. The reason for the macro +// definition at the top of the file is explained on the opt-in features page. +// Basically, it makes _to_string constexpr, but slows down compilation. +// +// You can also do things such as: constexpr size_t length(const char *s, size_t index = 0) { diff --git a/test/Makefile b/test/Makefile index e4bc01f..9e6cd57 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,12 +1,13 @@ +.PHONY : default +default : + make -C ../doc examples + python test.py + .PHONY : platform platform : make -C ../doc examples - python test.py + python test.py --all .PHONY : clean clean : rm -rf platform *.obj - -.PHONY : default -default : run - @: diff --git a/test/expect/101-special-values b/test/expect/101-special-values index d30c108..4bdc524 100644 --- a/test/expect/101-special-values +++ b/test/expect/101-special-values @@ -1 +1,2 @@ +Invalid Red diff --git a/test/expect/102-bitset b/test/expect/102-bitset index e69de29..09a6a9b 100644 --- a/test/expect/102-bitset +++ b/test/expect/102-bitset @@ -0,0 +1 @@ +0000000000000010100001 diff --git a/test/performance/4-declare_enums.cc b/test/performance/4-declare_enums.cc index b00f351..400e291 100644 --- a/test/performance/4-declare_enums.cc +++ b/test/performance/4-declare_enums.cc @@ -27,23 +27,13 @@ ENUM(APIMethod, int, WriteOwner, PollOwner, ReadProposal, WriteProposal, PollProposal, ReadHistory, WriteHistory, PollHistory) -ENUM(Region, int, - EasterEurope, CentralEurope, WesternEurope, Mediterranean, NorthAfrica, - MiddleEast, MiddleEastNorthAfrica, GreaterPersia, Balkans, Scandinavia, - NorhernEurope, BritishIsles, LatinEurope, Iberia, LowCountries, Baltics, - Yugoslavia, Caucasus, VolgaBasin, Urals, CentralAsia, Siberia, - RussianFarEast, EastAsia, SoutheastAsia, Indochina, SouthAsia, - IndianSubcontinent, Desi = IndianSubcontinent, Asia, Australasia, Oceania, - Micronesia, Polynesia, JapaneseIslands, Tibet, ArabianPeninsula, - HornOfAfrica, NearEast, AlSham, EastAfrica, NileBasin, Palestine, Levant, - Anatolia, AegeanSea, GreatSteppe, CongoBasin, SouthAfrica, WestAfrica, - Sahara, WestSahara, NorthwestTerritory, YukonAlaska, Quebec, NewEngland, - DeepSouth) - -int main() -{ - return 0; -} +ENUM(Lipsum, int, + Lorem, ipsum, dolor, sit, amet, consectetur, adipiscing, elit, Vivamus, + libero, massa, tincidunt, at, ex, nec, porta, malesuada, arcu, Nullam, + lectus, nibh, dictum, eget, convallis, ac, feugiat, felis, Suspendisse, + quis, purus, vel, lacus, cursus, tristique, Donec, augue, tortor, luctus, + a, sed, mattis, in, quam, Cras, vitae, euismod, Cum, sociis, natoque, + penatibus, et, magnis, dis, parturient) ENUM(ASTNode0, int, IntegerLiteral, StringLiteral, CharacterLiteral, Variable, UnaryOperation, @@ -224,3 +214,8 @@ ENUM(ASTNode29, int, BinaryOperation, ApplicationExpression, Abstraction, LetBinding, CaseExpression, Pattern, Signature, Module, Functor, TypeVariable, BasicType, ArrowType, VariantTypeConstant) + +int main() +{ + return 0; +}