Made N4428 implementation more exact.

See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4428.pdf
This commit is contained in:
Anton Bachin 2015-07-11 12:55:28 -05:00
parent 30efe7ccd7
commit f404ea709b
3 changed files with 293 additions and 70 deletions

View File

@ -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 &mdash; 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.

View File

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

View File

@ -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]; };
};
};