From f1a0b5d0e4b4a6693224b295915ddaad53bf6609 Mon Sep 17 00:00:00 2001 From: Anton Bachin Date: Thu, 9 Jul 2015 17:26:10 -0500 Subject: [PATCH] Simple implementation of N4428 enum reflection. --- doc/demo/105-c++17-reflection.md | 74 ++++++++++++++++++++++++++++++++ doc/template/cxx17.tmpl | 1 + doc/template/ref.tmpl | 2 +- example/105-c++17-reflection.cc | 62 ++++++++++++++++++++++++++ extra/better-enums/n4428.h | 46 ++++++++++++++++++++ test/CMakeLists.txt | 6 ++- test/expect/105-c++17-reflection | 1 + 7 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 doc/demo/105-c++17-reflection.md create mode 100644 doc/template/cxx17.tmpl create mode 100644 example/105-c++17-reflection.cc create mode 100644 extra/better-enums/n4428.h create mode 100644 test/expect/105-c++17-reflection diff --git a/doc/demo/105-c++17-reflection.md b/doc/demo/105-c++17-reflection.md new file mode 100644 index 0000000..18c5e03 --- /dev/null +++ b/doc/demo/105-c++17-reflection.md @@ -0,0 +1,74 @@ +## C++17 reflection + +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: + +- 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. + +With that out of the way, we can look at a simple example. + +[n4428]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4428.pdf +[slow-enum]: ${prefix}OptInFeatures.html#CompileTimeNameTrimming + +--- + +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. + + #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`: + + constexpr std::size_t size = + std::enum_traits<Channel>::enumerators::size; + + constexpr Channel value_0 = + std::enum_traits<Channel>::enumerators::get<0>::value; + constexpr Channel value_1 = + std::enum_traits<Channel>::enumerators::get<1>::value; + +Let's 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; + + return 0; + } + +That prints `Blue`, as you would expect. + +%% description = Approximate implementation of N4428 enum reflection based on +Better Enums. diff --git a/doc/template/cxx17.tmpl b/doc/template/cxx17.tmpl new file mode 100644 index 0000000..6f7f432 --- /dev/null +++ b/doc/template/cxx17.tmpl @@ -0,0 +1 @@ +C++17 \ No newline at end of file diff --git a/doc/template/ref.tmpl b/doc/template/ref.tmpl index 71172b4..8b25206 100644 --- a/doc/template/ref.tmpl +++ b/doc/template/ref.tmpl @@ -1 +1 @@ -0.10.1 \ No newline at end of file +master \ No newline at end of file diff --git a/example/105-c++17-reflection.cc b/example/105-c++17-reflection.cc new file mode 100644 index 0000000..875ef1a --- /dev/null +++ b/example/105-c++17-reflection.cc @@ -0,0 +1,62 @@ +// This file was generated automatically. + +// C++17 reflection +// +// 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: +// +// 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. +// +// With that out of the way, we can look at an example. + +// The implementation is defined in extra/better-enums/n4428.h. Let's assume +// that extra/ has been added as an include file path. + +#include +#include +#include + + +// Let's declare an enum: + +ENUM(Channel, char, Red = 1, Green, Blue) + +// N4428 requires three constexpr traits, of which we have two implemented +// exactly, that is, as constexpr: + +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: + +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; + + return 0; +} diff --git a/extra/better-enums/n4428.h b/extra/better-enums/n4428.h new file mode 100644 index 0000000..d9995c7 --- /dev/null +++ b/extra/better-enums/n4428.h @@ -0,0 +1,46 @@ +// This file is part of Better Enums, released under the BSD 2-clause license. +// See doc/LICENSE for details, or visit http://github.com/aantron/better-enums. + +// 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. + +// 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 + +#pragma once + +#ifndef BETTER_ENUMS__N4428_H +#define BETTER_ENUMS__N4428_H + + + +namespace std { + +template +struct enum_traits { + struct enumerators { + constexpr static const size_t size = Enum::_size(); + + template + struct get { + constexpr static Enum value = Enum::_values()[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. + static const char* identifier() { return Enum::_names()[Index]; }; + }; + }; +}; + +} + + + +#endif // #ifndef BETTER_ENUMS__N4428_H diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f0f39b6..b8e4e14 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -89,10 +89,11 @@ endforeach(TEST) set(EXAMPLES 1-hello-world 2-conversions 3-iterate 4-switch 5-iostreams 6-safety 7-representation 8-constexpr 101-special-values 102-any-underlying - 103-bitset 104-quine) + 103-bitset 104-quine 105-c++17-reflection) set(SKIPPED_FOR_CXX98 - 8-constexpr 101-special-values 102-any-underlying 103-bitset 104-quine) + 8-constexpr 101-special-values 102-any-underlying 103-bitset 104-quine + 105-c++17-reflection) set(SKIPPED_FOR_STRICT_CONVERSION 4-switch 102-any-underlying) @@ -112,6 +113,7 @@ endforeach(EXAMPLE) # Add compiler flags. include_directories(..) +include_directories(../extra) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES Clang) include(CheckCXXCompilerFlag) diff --git a/test/expect/105-c++17-reflection b/test/expect/105-c++17-reflection new file mode 100644 index 0000000..040b6da --- /dev/null +++ b/test/expect/105-c++17-reflection @@ -0,0 +1 @@ +Blue