diff --git a/doc/demo/105-c++17-reflection.md b/doc/demo/105-c++17-reflection.md index 18c5e03..3f58c4a 100644 --- a/doc/demo/105-c++17-reflection.md +++ b/doc/demo/105-c++17-reflection.md @@ -1,74 +1,188 @@ -## C++17 reflection +## C++17 reflection proposal -Better Enums can be used to approximately implement the enums portion of the -[$cxx17 reflection proposal N4428][n4428] in $cxx11. The implementation is -*approximate* in the following senses: +*You can try this demo [live online][live].* -- It only applies to Better Enums, not built-in enums. -- `enum_traits::enumerators::get::identifier` is a non-`constexpr` - function rather than a `constexpr` variable. I could make it a `constexpr` - variable as in the proposal, but that requires - [compile-time name trimming][slow-enum] to be enabled for the Better Enum - on which `get` is used. Since that's an opt-in feature, I can't guarantee it. - I preferred not to write feature-detection code, in order to keep the - implementation simple. -- The return type of `identifier` is `const char*` instead of the proposed - `std::string_literal`, because I don't have an implementation of the latter - available. I'm also ignoring the requirements on encoding, and just taking - whatever the preprocessor provides. +Better Enums can be used to implement the enums portion of the +[$cxx17 reflection proposal N4428][n4428] in $cxx11. N4428 proposes the +following traits interface: -With that out of the way, we can look at a simple example. +~~~comment +namespace std { -[n4428]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4428.pdf -[slow-enum]: ${prefix}OptInFeatures.html#CompileTimeNameTrimming +template +struct enum_traits { + struct enumerators { + constexpr static size_t size; + + template + struct get { + constexpr string_literal identifier; + constexpr static E value; + }; + }; +}; + +} +~~~ + +So, the basic usage would be: + +~~~comment +enum class Foo {A, B, C}; + +constexpr size_t size = + std::enum_traits::enumerators::size; + +constexpr Foo value_0 = + std::enum_traits::enumerators::get<0>::value; + +constexpr string_literal name_1 = + std::enum_traits::enumerators::get<1>::identifier; +~~~ + +Resulting in the values `3`, `Foo::A`, and `"B"`, respectively. --- -The implementation is defined in [`extra/better-enums/n4428.h`][header]. Let's -assume that `extra/` has been added as a directory to search for include files. +The optional Better Enums header file [`extra/better-enums/n4428.h`][header] +implements this interface, though with two necessary differences. + +1. The interface is only available for Better Enums, i.e. enums declared through + the `ENUM` macro. This is because the macro is what generates the information + necessary for reflection. +2. The type of `identifier` is `const char*`, not the ${cxx17} proposed + `string_literal`. + +### Demo + +So, with that out of the way, we can do a little test. Let's assume that +`extra/` has been added as a directory to search for include files. + + #ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING + #define BETTER_ENUMS_CONSTEXPR_TO_STRING + #endif #include #include <enum.h> #include <better-enums/n4428.h> -[header]: https://github.com/aantron/better-enums/blob/$ref/extra/better-enums/n4428.h - --- Let's declare an enum: ENUM(Channel, char, Red = 1, Green, Blue) -N4428 proposes three `constexpr` traits, of which we have two implemented -exactly — that is, as `constexpr`: +...and try N4428: - constexpr std::size_t size = + constexpr std::size_t size = std::enum_traits<Channel>::enumerators::size; - constexpr Channel value_0 = + constexpr Channel value_0 = std::enum_traits<Channel>::enumerators::get<0>::value; - constexpr Channel value_1 = + + constexpr Channel value_1 = std::enum_traits<Channel>::enumerators::get<1>::value; -Let's check the results: + constexpr const char *identifier_2 = + std::enum_traits<Channel>::enumerators::get<2>::identifier; + +...and check the results: static_assert(size == 3, ""); static_assert(value_0 == +Channel::Red, ""); static_assert(value_1 == +Channel::Green, ""); -Finally, we can try using `identifier`: - int main() { - std::cout - << std::enum_traits<Channel>::enumerators::get<2>::identifier() - << std::endl; - + std::cout << identifier_2 << std::endl; return 0; } That prints `Blue`, as you would expect. +--- + +So, Better Enums can be used in $cxx11 to test N4428, and maybe start developing +libraries on top of it. N4428 is very low-level, so it needs additional code +over it to be truly useful. Whether developing now is a good idea is debatable, +since N4428 is still only a proposal. But, it's an interesting thing to +consider. + +Also, Better Enums can be implemented almost completely *in terms of* N4428, so +the two interfaces are in some vaguely mathematical sense "equivalent." If N4428 +is accepted, I may implement a variant of Better Enums on top of it, since it +will remove many limitations. + +### Quirk + +The reason for the `#define` in the code above is that there is one quirk: +the interface above is available only for Better Enums for which +[compile-time name trimming][slow-enum] is enabled, i.e. those declared when +`BETTER_ENUMS_CONSTEXPR_TO_STRING` was defined, or declared with the `SLOW_ENUM` +variant of `ENUM`. As mentioned on the linked page, the reason compile-time name +trimming is not the default is that, while still pretty fast, it is four times +slower than program-startup-time name trimming. The latter is the default. + +Despite the above, a variation on the interface is available for enums without +compile-time name trimming: + +~~~comment +namespace std { + +template +struct enum_traits { + struct enumerators { + constexpr static size_t size; + + template + struct get { + constexpr const char *identifier; + constexpr static E value; + }; + + // For enums without compile-time name trimming. + template + struct get_alt { + static const char* identifier(); + constexpr static E value; + }; + }; +}; + +} +~~~ + +As you can see, the difference is that `identifier` is a non-`constexpr` +function, and you have to access it through `get_alt`. + +~~~comment +// Without compile-time name trimming. +ENUM(Depth, int, HighColor, TrueColor) + +int main() +{ + std::cout + << std::enum_traits::enumerators::get_alt<1>::identifier() + << std::endl; + + return 0; +} +~~~ + +### The future + +N4428 is the fourth in a series of revisions: [N3815][n3815], [N4027][n4027], +[N4113][n4113], N4428. If there are more revisions that change the proposal for +enums, I will try to implement those as well. + +[n4428]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4428.pdf +[n4113]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4113.pdf +[n4027]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4027.pdf +[n3815]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3815.html +[slow-enum]: ${prefix}OptInFeatures.html#CompileTimeNameTrimming +[header]: https://github.com/aantron/better-enums/blob/$ref/extra/better-enums/n4428.h +[live]: http://melpon.org/wandbox/permlink/pNEx7UEWqDtqFAwm + %% description = Approximate implementation of N4428 enum reflection based on Better Enums. diff --git a/example/105-c++17-reflection.cc b/example/105-c++17-reflection.cc index 875ef1a..b45eb1b 100644 --- a/example/105-c++17-reflection.cc +++ b/example/105-c++17-reflection.cc @@ -1,27 +1,60 @@ // This file was generated automatically. -// C++17 reflection +// C++17 reflection proposal // -// Better Enums can be used to approximately implement the enums portion of the -// C++17 reflection proposal N4428 in C++11. The implementation is approximate -// in the following senses: +// Better Enums can be used to implement the enums portion of the C++17 +// reflection proposal N4428 in C++11. N4428 proposes the following traits +// interface: // -// 1. It only applies to Better Enums, not built-in enums. -// 2. enum_traits::enumerators::get::identifier is a non-constexpr -// function rather than a constexpr variable. I could make it a constexpr -// variable as in the proposal, but that requires compile-time name -// trimming to be enabled for the Better Enum on which get is used. Since -// that's an opt-in feature, I can't guarantee it. I preferred not to write -// feature-detection code, in order to keep the implementation simple. -// 3. The return type of identifier is const char* instead of the proposed -// std::string_literal, because I don't have an implementation of the -// latter available. I'm also ignoring the requirements on encoding, and -// just taking whatever the preprocessor provides. +// namespace std { // -// With that out of the way, we can look at an example. +// template +// struct enum_traits { +// struct enumerators { +// constexpr static size_t size; +// +// template +// struct get { +// constexpr string_literal identifier; +// constexpr static E value; +// }; +// }; +// }; +// +// } +// +// So, the basic usage would be: +// +// enum class Foo {A, B, C}; +// +// constexpr size_t size = +// std::enum_traits::enumerators::size; +// +// constexpr Foo value_0 = +// std::enum_traits::enumerators::get<0>::value; +// +// constexpr string_literal name_1 = +// std::enum_traits::enumerators::get<1>::identifier; +// +// Resulting in the values 3, Foo::A, and "B", respectively. -// The implementation is defined in extra/better-enums/n4428.h. Let's assume -// that extra/ has been added as an include file path. +// The optional Better Enums header file extra/better-enums/n4428.h implements +// this interface, though with two necessary differences. +// +// 1. The interface is only available for Better Enums, i.e. enums declared +// through the ENUM macro. This is because the macro is what generates the +// information necessary for reflection. +// 2. The type of identifier is const char*, not the ${cxx17}-proposed +// string_literal. +// +// Demo +// +// So, with that out of the way, we can do a little test. Let's assume that +// extra/ has been added as a directory to search for include files. + +#ifndef BETTER_ENUMS_CONSTEXPR_TO_STRING +#define BETTER_ENUMS_CONSTEXPR_TO_STRING +#endif #include #include @@ -32,31 +65,101 @@ ENUM(Channel, char, Red = 1, Green, Blue) -// N4428 requires three constexpr traits, of which we have two implemented -// exactly, that is, as constexpr: +// ...and try N4428: constexpr std::size_t size = std::enum_traits::enumerators::size; constexpr Channel value_0 = std::enum_traits::enumerators::get<0>::value; + constexpr Channel value_1 = std::enum_traits::enumerators::get<1>::value; -// Let's check the results: +constexpr const char *identifier_2 = + std::enum_traits::enumerators::get<2>::identifier; + +// ...and check the results: static_assert(size == 3, ""); static_assert(value_0 == +Channel::Red, ""); static_assert(value_1 == +Channel::Green, ""); -// Finally, we can try using identifier: - int main() { - std::cout - << std::enum_traits::enumerators::get<2>::identifier() - << std::endl; - + std::cout << identifier_2 << std::endl; return 0; } + +// That prints Blue, as you would expect. + +// So, Better Enums can be used in C++11 to test N4428, and maybe start +// developing libraries on top of it. N4428 is very low-level, so it needs +// additional code over it to be truly useful. Whether developing now is a good +// idea is debatable, since N4428 is still only a proposal. But, it's an +// interesting thing to consider. +// +// Also, Better Enums can be implemented almost completely in terms of N4428, so +// the two interfaces are in some vaguely mathematical sense "equivalent." If +// N4428 is accepted, I may implement a variant of Better Enums on top of it, +// since it will remove many limitations. +// +// Quirk +// +// The reason for the #define in the code above is that there is one quirk: the +// interface above is available only for Better Enums for which compile-time +// name trimming is enabled, i.e. those declared when +// BETTER_ENUMS_CONSTEXPR_TO_STRING was defined, or declared with the SLOW_ENUM +// variant of ENUM. As mentioned on the linked page, the reason compile-time +// name trimming is not the default is that, while still pretty fast, it is four +// times slower than program-startup-time name trimming. The latter is the +// default. +// +// Despite the above, a variation on the interface is available for enums +// without compile-time name trimming: +// +// namespace std { +// +// template +// struct enum_traits { +// struct enumerators { +// constexpr static size_t size; +// +// template +// struct get { +// constexpr const char *identifier; +// constexpr static E value; +// }; +// +// // For enums without compile-time name trimming. +// template +// struct get_alt { +// static const char* identifier(); +// constexpr static E value; +// }; +// }; +// }; +// +// } +// +// As you can see, the difference is that identifier is a non-constexpr +// function, and you have to access it through get_alt. +// +// // Without compile-time name trimming. +// ENUM(Depth, int, HighColor, TrueColor) +// +// int main() +// { +// std::cout +// << std::enum_traits::enumerators::get_alt<1>::identifier() +// << std::endl; +// +// return 0; +// } +// +// The future +// +// N4428 is the fourth in a series of revisions: N3815, N4027, N4113, N4428. If +// there are more revisions that change the proposal for enums, I will try to +// implement those as well. diff --git a/extra/better-enums/n4428.h b/extra/better-enums/n4428.h index d9995c7..c4ef028 100644 --- a/extra/better-enums/n4428.h +++ b/extra/better-enums/n4428.h @@ -4,11 +4,11 @@ // This file provides an implementation of the enum reflection interface // proposed in // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4428.pdf -// over Better Enums. +// on top of Better Enums. // See further discussion and a demonstration of usage at // example/107-c++17-reflection.cc, or visit: -// http://aantron.github.io/better-enums/demo/C++17Reflection.html +// http://aantron.github.io/better-enums/demo/C++17ReflectionProposal.html #pragma once @@ -27,13 +27,19 @@ struct enum_traits { template struct get { constexpr static Enum value = Enum::_values()[Index]; + constexpr static const char *identifier = Enum::_names()[Index]; + }; - // In the proposal, this is a constexpr variable of type - // string_literal. It is possible to declare "identifier" that way - // with Better Enums, but only for enums that have compile-time name - // trimming enabled. Since compile-time name trimming currently has - // a severe performance impact, and is disabled by default, I chose - // to implement "identifier" as a function. + // The above declarations implement N4428 (except for const char* + // instead of string_literal). The alternative get below is needed for + // Better Enums that weren't generated with compile-time name trimming, + // i.e. without BETTER_ENUMS_CONSTEXPR_TO_STRING declared and not + // through the SLOW_ENUM macro. That is currently the default for + // Better Enums, since compile-time name trimming is pretty slow, so you + // would want to use get_alt instead of get. + template + struct get_alt { + constexpr static Enum value = Enum::_values()[Index]; static const char* identifier() { return Enum::_names()[Index]; }; }; };