[Better Enums] More complete release - feedback requested
Hello, I posted to the mailing list some weeks ago concerning a reflective enum library. I’ve since done a lot of work and made a new release. I’d like to get feedback from anyone that is interested. The library can be found at: https://github.com/aantron/better-enums At the risk of repeating myself, let me summarize for those who didn’t read the original thread: the library generates reflective enums that support string conversion and iteration, but with a nicer syntax than seen to date. Also, in C++11, the reflective capabilities are accessible at compile time. Since the original thread, I have made the library work with C++98 with the variadic macro extension, tested it thoroughly on clang, gcc, and msvc, rewrote the documentation, and made the interface (potentially) completely constexpr. The reason I say potentially is that there is one slow feature, which is disabled by default, but can be opted into easily. There is a tutorial starting here: http://aantron.github.io/better-enums/tutorial/HelloWorld.html and a few examples of “advanced” usage, the most interesting one of which I think is: http://aantron.github.io/better-enums/demo/SpecialValues.html (Thanks to Gottlob Frege for making a suggestion, which I adapted to improve this example.) I’d like to take this as far as possible towards solving the problems with C++ enum maintainability, and providing improved enum types that are as near to first-class as possible. I would be very happy if you just tried this out and let me know what you think and what problems you ran into. Thank you, Anton
Hi Anton, Anton Bachin wrote:
I posted to the mailing list some weeks ago concerning a reflective enum library. I've since done a lot of work and made a new release. I'd like to get feedback from anyone that is interested.
The library can be found at: https://github.com/aantron/better-enums
On things that I noticed in your docs is that the complexity of your string-to-enum conversion is linear; presumably you're either creating a sequence of ifs or doing a linear scan through an unsorted array. Making this more efficient would be an interesting challenge for metaprogrammers. I can think of three approaches that you could take: 1. Do a compile-time sort of your strings, so that you can do a binary search on them: 1a - Create a tree of ifs 1b - Create a sorted array of literals 2. Do a compile-time hash of your strings, and switch on the run-time hash. 3. Create a parser containing nested switch statements that parse the string character by character. Since we're reviewing Hana this week, it would be great to see how it could help with this. I have had real applications where this sort of conversion, i.e. essentially wanting to use a string in a switch statement, was a significant performance constraint. Regards, Phil
Whoops! Missed this because of the change in topic.
On things that I noticed in your docs is that the complexity of your string-to-enum conversion is linear; presumably you're either creating a sequence of ifs or doing a linear scan through an unsorted array.
Making this more efficient would be an interesting challenge for metaprogrammers.
I can think of three approaches that you could take:
1. Do a compile-time sort of your strings, so that you can do a binary search on them: 1a - Create a tree of ifs 1b - Create a sorted array of literals
2. Do a compile-time hash of your strings, and switch on the run-time hash.
3. Create a parser containing nested switch statements that parse the string character by character.
Since we're reviewing Hana this week, it would be great to see how it could help with this. I have had real applications where this sort of conversion, i.e. essentially wanting to use a string in a switch statement, was a significant performance constraint.
That’s right, for now I am simply doing a linear scan through an array of strings. Changing the complexity to sub-linear is on my list of things to do, and I’ve considered those approaches (compile-time binary search, hashing, and trie). So far, I’ve been focusing on the interface and generation procedure instead, so I didn’t want to get into too much optimization before I knew exactly what the constraints would be. It’s getting pretty stable now, so I would be grateful for any comments/assistance with compile-time data structures. Regards, Anton
On 9/06/2015 03:01, Anton Bachin wrote:
That’s right, for now I am simply doing a linear scan through an array of strings. Changing the complexity to sub-linear is on my list of things to do, and I’ve considered those approaches (compile-time binary search, hashing, and trie). So far, I’ve been focusing on the interface and generation procedure instead, so I didn’t want to get into too much optimization before I knew exactly what the constraints would be. It’s getting pretty stable now, so I would be grateful for any comments/assistance with compile-time data structures.
Don't forget to measure performance. Given that enums tend to have a very low N, when I made a superficially similar static map with a runtime string index scan it turned out to be faster to just do the linear scan than to try to compute hashes or do more complex searches. (As an aside, it was quite a handy data structure. It was mainly intended for string-to-enum conversions but because of the way it was templated it could be abused to do string-to-member-pointer conversions as well, which enabled all sorts of interesting reflection scenarios.)
On Jun 8, 2015, at 20:04, Gavin Lambert
wrote: Don't forget to measure performance. Given that enums tend to have a very low N, when I made a superficially similar static map with a runtime string index scan it turned out to be faster to just do the linear scan than to try to compute hashes or do more complex searches.
Of course :) One of the secondary reasons I gave data structures a low priority is because I have a hunch that the benefit of more complex data structures will be very low compared to the compilation time penalty, and will apply very rarely in practice. It is something I plan to explore and measure, however, so I can have a conclusive answer either way.
(As an aside, it was quite a handy data structure. It was mainly intended for string-to-enum conversions but because of the way it was templated it could be abused to do string-to-member-pointer conversions as well, which enabled all sorts of interesting reflection scenarios.)
I am curious to know what this was, it sounds interesting.
On 9/06/2015 13:16, Anton Bachin wrote:
(As an aside, it was quite a handy data structure. It was mainly intended for string-to-enum conversions but because of the way it was templated it could be abused to do string-to-member-pointer conversions as well, which enabled all sorts of interesting reflection scenarios.)
I am curious to know what this was, it sounds interesting.
It wasn't really all that interesting. It was just a "data table class"
with some helper macros that let you define a static const structure
private to a .cpp file (and initialised at compile time) to contain the
string and value, plus a "lookup class" that was declared in the .h (as
a static global) that was initialised with the address of the table.
All the actual searching was done at runtime by the lookup class, with
the table class basically just POD.
The table was normally declared using some macro helpers, so:
EnumLookup::Table FooStatusTable =
{
ELT_ENTRY(Normal),
ELT_ENTRY(Warning),
ELT_ENTRY(Error),
ELT_TERMINATOR
};
EnumLookup Foo::StatusLookup(&FooStatusTable);
But you could do weirder things with it, such as giving alternate names
(occasionally useful for serialisation upgrade scenarios or alternate
output formats):
EnumLookup::Table Foo::StatusTable =
{
ELT_NAMED_ENTRY(Normal, "Ok"),
ELT_NAMED_ENTRY(Warning, "Warn"),
ELT_ENTRY(Error),
ELT_TERMINATOR
};
And it had support to let you round-trip enum -> string -> enum values
that weren't listed in the table.
Because it was based on a template, you could also use member pointers
as the value to look up, for basic reflection support:
BarPropertyMapping::Table Bar::PropertyTable =
{
{ &Bar::Count, "Count" },
{ &Bar::Min, "Minimum" },
{ &Bar::Max, "Maximum" },
{ 0, NULL }
};
(You could use anything else as the value too, but these two seemed to
be the most useful in practice. This particular case only helped when
there were many members with the same type, of course, but it let them
be accessed "nicer" than just using a runtime map
On Jun 8, 2015, at 21:27, Gavin Lambert
wrote: On 9/06/2015 13:16, Anton Bachin wrote:
(As an aside, it was quite a handy data structure. It was mainly intended for string-to-enum conversions but because of the way it was templated it could be abused to do string-to-member-pointer conversions as well, which enabled all sorts of interesting reflection scenarios.)
I am curious to know what this was, it sounds interesting.
It wasn't really all that interesting. It was just a "data table class" with some helper macros that let you define a static const structure private to a .cpp file (and initialised at compile time) to contain the string and value, plus a "lookup class" that was declared in the .h (as a static global) that was initialised with the address of the table. All the actual searching was done at runtime by the lookup class, with the table class basically just POD. <snip>
That’s useful to know, thank you. I’d like to make Better Enums the join (meet?) of good approaches, so your message was helpful both for the method and for the use cases. I’ve been thinking about the serialization scenario, in particular. I don’t know the full range of what you needed, but would something like the ability to alias constants to each other, and iterate over unique values only, have been sufficient? For example, a declaration like …OldName, MoreNames, NewName = OldName, … where OldName is for compatibility, and NewName is what you intend to use in your code. Converting the string "OldName" results in the same numeric value as NewName. When iterated over as values, the value of OldName/NewName would get visited only once. Also, when converting to string, there could be some convention such as the first name declared is the preferred one (actually the opposite of what I wrote above).
On 9/06/2015 14:53, Anton Bachin wrote:
I’ve been thinking about the serialization scenario, in particular. I don’t know the full range of what you needed, but would something like the ability to alias constants to each other, and iterate over unique values only, have been sufficient? For example, a declaration like
…OldName, MoreNames, NewName = OldName, …
where OldName is for compatibility, and NewName is what you intend to use in your code. Converting the string "OldName" results in the same numeric value as NewName.
When iterated over as values, the value of OldName/NewName would get visited only once. Also, when converting to string, there could be some convention such as the first name declared is the preferred one (actually the opposite of what I wrote above).
Yeah, that's essentially how my one worked. You could list the same value multiple times with different strings, and when looking up by name both the old and the new names would resolve to the same value, and when looking up by value the first matching one listed in the table would be the string returned. I did have an enumeration feature but I don't recall if I made it smart enough to avoid listing "duplicates". I don't think it came up all that often in my particular use case. The other interesting thing that you could do though was to have separate tables for separate contexts. So for example with the same actual enum, you could use one string table when reading/writing JSON and a different table when reading/writing XML -- or one string for internal use and another string for user display. That sort of thing. (Although there are better ways to handle the latter, especially once you start considering multiple languages.)
Gavin Lambert wrote:
On 9/06/2015 03:01, Anton Bachin wrote:
That's right, for now I am simply doing a linear scan through an array of strings. Changing the complexity to sub-linear is on my list of things to do, and I've considered those approaches (compile-time binary search, hashing, and trie).
Don't forget to measure performance. Given that enums tend to have a very low N, when I made a superficially similar static map with a runtime string index scan it turned out to be faster to just do the linear scan than to try to compute hashes or do more complex searches.
In my case I was processing a file with key-value data; I needed to convert the key string to an enum. There were about 25 keys that I was interested in. Initially I had a sequence of if statements each doing a std::string::operator==(const char*). I changed that to a switch statement on the first character, followed by std::string:: operator==(const char*) for each of the keys with that initial letter. This increased the speed of that code 50X, and the speed of the overall file processing 2X. It was definitely worthwhile. There is an important special case - when the string is not one of the valid keys. If this is frequent in a particular application, a linear scan will do especially badly. Regards, Phil.
On Jun 8, 2015, at 23:30, Gavin Lambert
wrote:
The other interesting thing that you could do though was to have separate tables for separate contexts. So for example with the same actual enum, you could use one string table when reading/writing JSON and a different table when reading/writing XML -- or one string for internal use and another string for user display. That sort of thing. (Although there are better ways to handle the latter, especially once you start considering multiple languages.)
I think this is the only thing that’s not straightforward for me to support. I’ll give it some thought. There might be something that can be written over the core of Better Enums that does this nicely.
On Jun 9, 2015, at 09:08, Phil Endecott
wrote: In my case I was processing a file with key-value data; I needed to convert the key string to an enum. There were about 25 keys that I was interested in. Initially I had a sequence of if statements each doing a std::string::operator==(const char*). I changed that to a switch statement on the first character, followed by std::string:: operator==(const char*) for each of the keys with that initial letter. This increased the speed of that code 50X, and the speed of the overall file processing 2X. It was definitely worthwhile. There is an important special case - when the string is not one of the valid keys. If this is frequent in a particular application, a linear scan will do especially badly.
That’s a very good point.
On 10/06/2015 06:42, Anton Bachin wrote:
On Jun 8, 2015, at 23:30, Gavin Lambert
wrote: The other interesting thing that you could do though was to have separate tables for separate contexts. So for example with the same actual enum, you could use one string table when reading/writing JSON and a different table when reading/writing XML -- or one string for internal use and another string for user display. That sort of thing. (Although there are better ways to handle the latter, especially once you start considering multiple languages.) I think this is the only thing that’s not straightforward for me to support. I’ll give it some thought. There might be something that can be written over the core of Better Enums that does this nicely.
It was occasionally useful, but hardly essential. Certainly most languages that provide built-in enum-to-string conversions don't provide this. So don't worry about it too much. :)
On 06-06-2015 05:21, Anton Bachin wrote:
Hello,
I posted to the mailing list some weeks ago concerning a reflective enum library. I’ve since done a lot of work and made a new release. I’d like to get feedback from anyone that is interested.
Some suggestions: A. get rid of the leading _ un function names. B. make size a constexpr function C. add to_integral() (or is that done by a static cast?) Somthing to consider: One reaching for an enum, one very often needs a small statically sized, immutable bidirectional map. That is, the mapped strings needs not be the name of the enum constant. Key-value pair iteration may be useful and Thanks -Thorsten PS: I assume you might have seen http://www.codeproject.com/Articles/318690/C-Non-intrusive-enum-class-with-r... and https://svn.tuebingen.mpg.de/kyb-robotics/TeleKyb/trunk/stacks/telekyb_commo... ?
Hi, Thanks for the comments.
A. get rid of the leading _ un function names.
The reason I chose to add the _ is that the function names occupy the same namespace (I don’t mean the C++ keyword) as the constant names, i.e. if I had a function such as “Channel::valid()" it would conflict with a potential user-declared constant Channel::valid. I’m open to better suggestions than using underscores, though. I had an alternative implementation (which is internally almost the same), in which the ENUM macro generated type traits over an enum (or enum class, in C++11), instead of wrapping an enum in an object, as now. That is, instead of notionally generating struct Channel { enum _enumerated { … }; // Methods }; it generated enum class Channel { … }; template <> struct traits<Channel> { // Methods }; In this implementation, the methods were in their own namespace, so there was no need to use underscores. Also, there was no need to have operator+, and the interface, in general, was very uniform. However, the usage was usually much more verbose: Channel::_from_string(“Red”); vs. better_enums::traits<Channel>::from_string(“Red”); I could get some degree of type inference to happen by wrapping the traits functions in freestanding template functions, but it was still too verbose (IMO), and also threw away the advantage of a uniform and easy-to-remember interface. There might be a way to make this work nicely - I am open to suggestions. It might actually be possible to have decent syntax with traits, using some helper types to get type inference, along the lines suggested by Tony Van Eerd in an earlier message. I will look into that. However, I am working on a feature that allows the underlying type of a Better Enum to be any literal type that provides conversions to/from an integral type. It’s essentially an adapter between anything and the switch statement. That feature would not be possible with a traits approach in the form I outlined above.
B. make size a constexpr function
What would be the advantage to this? Uniformity of interface? Is there any disadvantage from this change? I generally want to move in the direction of making more things available to C++03 metaprogramming, so I am worried about making constants into functions.
C. add to_integral() (or is that done by a static cast?)
Do you mean this? http://aantron.github.io/better-enums/ApiReference.html#_to_integral I didn’t put it into the example in the README/documentation front page because it’s not much of a feature, and I’d rather save space. Do let me know if you think that’s misleading on my part.
Somthing to consider: One reaching for an enum, one very often needs a small statically sized, immutable bidirectional map. That is, the mapped strings needs not be the name of the enum constant. Key-value pair iteration may be useful and
Noted, thanks. I think this could be covered by a solution that would also take care of a use case Gavin Lambert described yesterday. I will look into it. And, thanks for the links. I did see the first one, but not the second. Best, Anton
On 10 June 2015 at 18:39, Anton Bachin
Hi,
Thanks for the comments.
A. get rid of the leading _ un function names.
+1
[snip]
Channel::_from_string(“Red”);
vs. better_enums::traits<Channel>::from_string(“Red”);
I could get some degree of type inference to happen by wrapping the traits functions in freestanding template functions, but it was still too verbose (IMO), and also threw away the advantage of a uniform and easy-to-remember interface.
I would prefer free-functions in a namespace. Something like: better_enums::from_string<Channel>("Red"); (although I think the namespace is too long. Maybe just 'enums'?)
[snip]
B. make size a constexpr function
+1
What would be the advantage to this? Uniformity of interface? Is there any disadvantage from this change? I generally want to move in the direction of making more things available to C++03 metaprogramming, so I am worried about making constants into functions.
I think you should look to the future and give pay-off to people using more modern C++. However you can macro-ize the constexpr keyword so that it is constexpr is supported or nothing if not. BOOST_NO_CXX11_CONSTEXPR will do the check for you.
On Jun 11, 2015, at 03:12, Sam Kellett
wrote: Channel::_from_string(“Red”);
vs. better_enums::traits<Channel>::from_string(“Red”);
I could get some degree of type inference to happen by wrapping the traits functions in freestanding template functions, but it was still too verbose (IMO), and also threw away the advantage of a uniform and easy-to-remember interface.
I would prefer free-functions in a namespace. Something like:
better_enums::from_string<Channel>("Red");
(although I think the namespace is too long. Maybe just 'enums’?)
That’s exactly what I ended up with. Ok, I’ll update and publish the traits branch again in a few days, and adapt some examples so that it’s easy to compare.
[snip]
B. make size a constexpr function
+1
I think you should look to the future and give pay-off to people using more modern C++. However you can macro-ize the constexpr keyword so that it is constexpr is supported or nothing if not.
BOOST_NO_CXX11_CONSTEXPR will do the check for you.
I’ve already done this, and I use it throughout enum.h. It’s not a problem to make a size() function constexpr only when supported, I am just not so comfortable throwing away size as an integral constant for non-C++11 users. Perhaps I can make size() a function and provide an alternative constant with an ugly name for those who might need it? Do people still use C++03? And, again, what is the rationale for size being a function?
On 11-06-2015 15:47, Anton Bachin wrote:
[snip]
B. make size a constexpr function
+1
I think you should look to the future and give pay-off to people using more modern C++. However you can macro-ize the constexpr keyword so that it is constexpr is supported or nothing if not.
BOOST_NO_CXX11_CONSTEXPR will do the check for you.
I’ve already done this, and I use it throughout enum.h. It’s not a problem to make a size() function constexpr only when supported, I am just not so comfortable throwing away size as an integral constant for non-C++11 users. Perhaps I can make size() a function and provide an alternative constant with an ugly name for those who might need it? Do people still use C++03? And, again, what is the rationale for size being a function?
It's more generic if you want to view the class as a container with size() and begin()/end(). Yes, people still used C++03, but I'm sure they can live with a non-constexpr size() that is every bit as fast at runtime as a constexpr one. -Thorsten
Thanks for the reply :)
On Jun 11, 2015, at 09:46, Thorsten Ottosen
wrote: On 11-06-2015 15:47, Anton Bachin wrote:
[snip]
B. make size a constexpr function
+1
I think you should look to the future and give pay-off to people using more modern C++. However you can macro-ize the constexpr keyword so that it is constexpr is supported or nothing if not.
BOOST_NO_CXX11_CONSTEXPR will do the check for you.
I’ve already done this, and I use it throughout enum.h. It’s not a problem to make a size() function constexpr only when supported, I am just not so comfortable throwing away size as an integral constant for non-C++11 users. Perhaps I can make size() a function and provide an alternative constant with an ugly name for those who might need it? Do people still use C++03? And, again, what is the rationale for size being a function?
It's more generic if you want to view the class as a container with size() and begin()/end().
Better Enums is not the container, however. It’s a class that provides access to two containers, currently through _names() and _values(). Those containers do have size() (a function), and begin()/end(): http://aantron.github.io/better-enums/ApiReference.html#Typedef_value_iterab... http://aantron.github.io/better-enums/ApiReference.html#Typedef_name_iterabl... These are containers in the sense you mean, compatible with STL algorithms, for-each loops, etc. I do value having size be a function for uniformity, so that it’s not surprising that it’s a function in the containers, yet a constant in the enum. I’m leaning towards your suggestion, and providing a constant with an alternative name for C++03 metaprogramming.
Yes, people still used C++03, but I'm sure they can live with a non-constexpr size() that is every bit as fast at runtime as a constexpr one.
Well, the issue isn’t runtime performance, it’s that you can’t use the function for some purposes before C++11. For example, only the first one works: const int size = 3; char *descriptions[size]; int size() { return 3; } char *descriptions[size()]; I may be somehow wrong with this usage example. If so, please correct me. Regards, Anton
On 11-06-2015 17:06, Anton Bachin wrote:
Thanks for the reply :)
On Jun 11, 2015, at 09:46, Thorsten Ottosen
wrote:
It's more generic if you want to view the class as a container with size() and begin()/end().
Better Enums is not the container, however. It’s a class that provides access to two containers, currently through _names() and _values(). Those containers do have size() (a function), and begin()/end():
Well, as I said, I think it makes sense to view it as a special type of map.
const int size = 3; char *descriptions[size];
int size() { return 3; } char *descriptions[size()];
I may be somehow wrong with this usage example. If so, please correct me.
If that is an important use-case, then I suggest that we provide both size() and enum_size constant. regards Thorsten
Better Enums is not the container, however. It’s a class that provides access to two containers, currently through _names() and _values(). Those containers do have size() (a function), and begin()/end():
Well, as I said, I think it makes sense to view it as a special type of map.
A secondary reason, by the way, why I didn’t make enums themselves be containers is that enums are types instead of values, whereas containers in typical C++ libraries are values. I don’t think this could be made to work: for (auto channel : Channel) { … }
Sam Kellett wrote:
I would prefer free-functions in a namespace. Something like:
better_enums::from_string<Channel>("Red");
I'm not a big fan of lexical_cast, but it should be noted that if Channel defines operator>>, the above could be spelled lexical_cast<Channel>("Red").
Oops, I lost this message due to its compact vertical size in my mail client :) Scrolled right past it.. sorry.
On Jun 11, 2015, at 09:33, Peter Dimov
wrote: Sam Kellett wrote:
I would prefer free-functions in a namespace. Something like:
better_enums::from_string<Channel>("Red");
I'm not a big fan of lexical_cast, but it should be noted that if Channel defines operator>>, the above could be spelled lexical_cast<Channel>("Red").
I’m planning to overload the stream operators, but probably in a separate header file. The reason is that including <iostream> slows down compilation by an order of magnitude or so compared to just the current contents of enum.h, when used for a few enums. Do you think I should just not bother with this, and include it anyway? Thanks, Anton
Anton Bachin wrote:
I’m planning to overload the stream operators, but probably in a separate header file. The reason is that including <iostream> slows down compilation by an order of magnitude or so compared to just the current contents of enum.h, when used for a few enums. Do you think I should just not bother with this, and include it anyway?
<iosfwd> should be enough, I think.
On Jun 11, 2015, at 10:22, Peter Dimov
wrote: Anton Bachin wrote:
I’m planning to overload the stream operators, but probably in a separate header file. The reason is that including <iostream> slows down compilation by an order of magnitude or so compared to just the current contents of enum.h, when used for a few enums. Do you think I should just not bother with this, and include it anyway?
<iosfwd> should be enough, I think.
Perfect, this is the kind of thing I was hoping for. Thanks!
On Jun 11, 2015, at 10:25, Anton Bachin
wrote: I’m planning to overload the stream operators, but probably in a separate header file. The reason is that including <iostream> slows down compilation by an order of magnitude or so compared to just the current contents of enum.h, when used for a few enums. Do you think I should just not bother with this, and include it anyway?
<iosfwd> should be enough, I think.
Perfect, this is the kind of thing I was hoping for. Thanks!
Well. <iosfwd> turned out to be not so useful in a header-only library. Including it allows you to declare overloads, but not to (easily) define them, because you need various iostreams definitions and additional declarations. I did timing tests on some headers, and including anything from iostreams except <iosfwd> carried a significant penalty, well within a factor of 2 of <iostream> itself. I may have to move the operators to a separate file, but for now I put their definitions in enum.h, made them into templates, and hid the undefined iostreams types behind templates that depend on the overall template parameter. This prevents the compiler from checking the types in the operator definitions unless and until the operators actually get used. It looks very ridiculous. It does, however, keep everything in one header, with no penalty for those not using iostreams. Has anyone seen a better way? https://github.com/aantron/better-enums/blob/0f636671/enum.h#L1044
Anton Bachin wrote:
Well. <iosfwd> turned out to be not so useful in a header-only library. Including it allows you to declare overloads, but not to (easily) define them, because you need various iostreams definitions and additional declarations. I did timing tests on some headers, and including anything from iostreams except <iosfwd> carried a significant penalty, well within a factor of 2 of <iostream> itself.
I may have to move the operators to a separate file, but for now I put their definitions in enum.h, made them into templates, and hid the undefined iostreams types behind templates that depend on the overall template parameter.
Streams are already templated, so artificial templating shouldn't be necessary if you use basic_istream and basic_ostream.
On Jun 12, 2015, at 05:51, Peter Dimov
wrote: Well. <iosfwd> turned out to be not so useful in a header-only library. Including it allows you to declare overloads, but not to (easily) define them, because you need various iostreams definitions and additional declarations. I did timing tests on some headers, and including anything from iostreams except <iosfwd> carried a significant penalty, well within a factor of 2 of <iostream> itself.
I may have to move the operators to a separate file, but for now I put their definitions in enum.h, made them into templates, and hid the undefined iostreams types behind templates that depend on the overall template parameter.
Streams are already templated, so artificial templating shouldn't be necessary if you use basic_istream and basic_ostream.
But the compiler is able to instantiate those eagerly if a type argument is specified (char) that does not depend on the outer template parameter. I did a little test, and using basic_istream<char> produces the same result as istream - compilation error if <istream> is not included. It seems I still have to wrap either basic_istream<char>, or just the type argument char. Ahh, <iosfwd> :) Regards, Anton
Anton Bachin wrote:
On Jun 12, 2015, at 05:51, Peter Dimov
wrote: Streams are already templated, so artificial templating shouldn't be necessary if you use basic_istream and basic_ostream.
But the compiler is able to instantiate those eagerly if a type argument is specified (char) that does not depend on the outer template parameter.
It is. What I meant was:
template
On Jun 12, 2015, at 09:30, Peter Dimov
wrote: But the compiler is able to instantiate those eagerly if a type argument is specified (char) that does not depend on the outer template parameter.
It is. What I meant was: <snip>
template
inline std::basic_ostream & operator <<(std::basic_ostream & os, const Enum& value) { return os << value.template _to_basic_string<Ch>(); } You'll have to define _to_basic_string for char, wchar_t, char16_t and char32_t if you want to cover all cases, of course.
If you want to restrict yourself to char-based streams:
template
inline std::basic_ostream & operator <<(std::basic_ostream & os, const Enum& value) { return os << value._to_string(); }
Ah, cool, yes. As long as I only actually support char streams, this looks
almost like an eta-expansion. I was able to use it on std::string as well
(std::basic_string<Char>), and replaced the reference to ios_base::failbit with
std::basic_istream
participants (6)
-
Anton Bachin
-
Gavin Lambert
-
Peter Dimov
-
Phil Endecott
-
Sam Kellett
-
Thorsten Ottosen