mirror of
https://github.com/aantron/better-enums.git
synced 2026-01-01 03:12:09 +08:00
Made N4428 implementation more exact.
See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4428.pdf
This commit is contained in:
parent
30efe7ccd7
commit
f404ea709b
@ -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<E>::enumerators::get<I>::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
|
||||
<em>namespace std {
|
||||
|
||||
[n4428]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4428.pdf
|
||||
[slow-enum]: ${prefix}OptInFeatures.html#CompileTimeNameTrimming
|
||||
template <typename E>
|
||||
struct enum_traits {
|
||||
struct enumerators {
|
||||
constexpr static size_t size;
|
||||
|
||||
template <size_t I>
|
||||
struct get {
|
||||
constexpr string_literal identifier;
|
||||
constexpr static E value;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
}</em>
|
||||
~~~
|
||||
|
||||
So, the basic usage would be:
|
||||
|
||||
~~~comment
|
||||
<em>enum class Foo {A, B, C};
|
||||
|
||||
constexpr size_t size =
|
||||
std::enum_traits<Foo>::enumerators::size;
|
||||
|
||||
constexpr Foo value_0 =
|
||||
std::enum_traits<Foo>::enumerators::get<0>::value;
|
||||
|
||||
constexpr string_literal name_1 =
|
||||
std::enum_traits<Foo>::enumerators::get<1>::identifier;</em>
|
||||
~~~
|
||||
|
||||
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 <iostream>
|
||||
<em>#include</em> <<em>enum.h</em>>
|
||||
<em>#include</em> <<em>better-enums/n4428.h</em>>
|
||||
|
||||
[header]: https://github.com/aantron/better-enums/blob/$ref/extra/better-enums/n4428.h
|
||||
|
||||
---
|
||||
|
||||
Let's declare an enum:
|
||||
|
||||
<em>ENUM</em>(<em>Channel</em>, <em>char</em>, <em>Red</em> = <em>1</em>, <em>Green</em>, <em>Blue</em>)
|
||||
|
||||
N4428 proposes three `constexpr` traits, of which we have two implemented
|
||||
exactly — that is, as `constexpr`:
|
||||
...and try N4428:
|
||||
|
||||
<em>constexpr std::size_t size</em> =
|
||||
constexpr std::size_t <em>size</em> =
|
||||
<em>std</em>::<em>enum_traits</em><<em>Channel</em>>::<em>enumerators</em>::<em>size</em>;
|
||||
|
||||
<em>constexpr Channel value_0</em> =
|
||||
constexpr Channel <em>value_0</em> =
|
||||
<em>std</em>::<em>enum_traits</em><<em>Channel</em>>::<em>enumerators</em>::<em>get</em><<em>0</em>>::<em>value</em>;
|
||||
<em>constexpr Channel value_1</em> =
|
||||
|
||||
constexpr Channel <em>value_1</em> =
|
||||
<em>std</em>::<em>enum_traits</em><<em>Channel</em>>::<em>enumerators</em>::<em>get</em><<em>1</em>>::<em>value</em>;
|
||||
|
||||
Let's check the results:
|
||||
constexpr const char *<em>identifier_2</em> =
|
||||
<em>std</em>::<em>enum_traits</em><<em>Channel</em>>::<em>enumerators</em>::<em>get</em><<em>2</em>>::<em>identifier</em>;
|
||||
|
||||
...and check the results:
|
||||
|
||||
static_assert(<em>size</em> == <em>3</em>, "");
|
||||
|
||||
static_assert(<em>value_0</em> == +<em>Channel::Red</em>, "");
|
||||
static_assert(<em>value_1</em> == +<em>Channel::Green</em>, "");
|
||||
|
||||
Finally, we can try using `identifier`:
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout
|
||||
<< <em>std</em>::<em>enum_traits</em><<em>Channel</em>>::<em>enumerators</em>::<em>get</em><<em>2</em>>::<em>identifier</em>()
|
||||
<< std::endl;
|
||||
|
||||
std::cout << <em>identifier_2</em> << 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
|
||||
<em>namespace std {
|
||||
|
||||
template <typename E>
|
||||
struct enum_traits {
|
||||
struct enumerators {
|
||||
constexpr static size_t size;
|
||||
|
||||
template <size_t I>
|
||||
struct get {
|
||||
constexpr const char *identifier;
|
||||
constexpr static E value;
|
||||
};
|
||||
|
||||
</em>// For enums without compile-time name trimming.<em>
|
||||
template <size_t I>
|
||||
struct get_alt {
|
||||
static const char* identifier();
|
||||
constexpr static E value;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
}</em>
|
||||
~~~
|
||||
|
||||
As you can see, the difference is that `identifier` is a non-`constexpr`
|
||||
function, and you have to access it through `get_alt<I>`.
|
||||
|
||||
~~~comment
|
||||
// Without compile-time name trimming.
|
||||
<em>ENUM(Depth, int, HighColor, TrueColor)
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout
|
||||
<< std::enum_traits<Depth>::enumerators::get_alt<1>::identifier()
|
||||
<< std::endl;
|
||||
|
||||
return 0;
|
||||
}</em>
|
||||
~~~
|
||||
|
||||
### 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.
|
||||
|
||||
@ -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<E>::enumerators::get<I>::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 <typename E>
|
||||
// struct enum_traits {
|
||||
// struct enumerators {
|
||||
// constexpr static size_t size;
|
||||
//
|
||||
// template <size_t I>
|
||||
// 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<Foo>::enumerators::size;
|
||||
//
|
||||
// constexpr Foo value_0 =
|
||||
// std::enum_traits<Foo>::enumerators::get<0>::value;
|
||||
//
|
||||
// constexpr string_literal name_1 =
|
||||
// std::enum_traits<Foo>::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 <iostream>
|
||||
#include <enum.h>
|
||||
@ -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<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:
|
||||
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 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 <typename E>
|
||||
// struct enum_traits {
|
||||
// struct enumerators {
|
||||
// constexpr static size_t size;
|
||||
//
|
||||
// template <size_t I>
|
||||
// struct get {
|
||||
// constexpr const char *identifier;
|
||||
// constexpr static E value;
|
||||
// };
|
||||
//
|
||||
// // For enums without compile-time name trimming.
|
||||
// template <size_t I>
|
||||
// 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<I>.
|
||||
//
|
||||
// // Without compile-time name trimming.
|
||||
// ENUM(Depth, int, HighColor, TrueColor)
|
||||
//
|
||||
// int main()
|
||||
// {
|
||||
// std::cout
|
||||
// << std::enum_traits<Depth>::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.
|
||||
|
||||
@ -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 <size_t Index>
|
||||
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 <size_t Index>
|
||||
struct get_alt {
|
||||
constexpr static Enum value = Enum::_values()[Index];
|
||||
static const char* identifier() { return Enum::_names()[Index]; };
|
||||
};
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user