aantron / better-enums

C++ compile-time enum to string, iteration, in a single header file
http://aantron.github.io/better-enums
BSD 2-Clause "Simplified" License
1.67k stars 173 forks source link

setting enumerations from strings #50

Open JaapAap opened 7 years ago

JaapAap commented 7 years ago

I am migrating from a 'smart enum' library based on what used to be 'BOOST_ENUM'. I noticed that the _from_string function is static, and recall having been bitten by this before in that library.

My problem is that it is possible to call this as a member function on an existing enumeration with rather unpleasant consequences.

BETTER_ENUM(Enum, int, A, B, C)
Enum X = Enum::_from_string("A") 
X._from_string("B"); // legal, but no change of X

I would much prefer the function to be non-static, in order to make sure that at the end X == Enum::B holds, and (for example) having a factory function akin to what is present in the standard library, such as:

X = make_enum<Enum>("A"); 
X = make_enum<Enum>(2); 

Also, I am missing constructors that allow for example:

Enum X("A")

Is there a good reason to not allow this?

aantron commented 7 years ago

I think it's ultimately a matter of preference. I think something close to what you are proposing is also discussed in Should Better Enums provide enum "objects" or traits types? Depending on what exactly you'd like to do, there may be some small technical limitations. I think you're proposing something between the current "object" interpretation and a full-on traits-only interpretation, so to answer your question usefully, I'd (we'd) have to look in total at what would be implemented where and how.

You're welcome to experiment. You can use that question as a list of things to watch out for – I think it's all I know. You can also glance in the branch that the question mentions. Though the branch is quite outdated, it's good enough to get an idea everything that needs to go into a partial- or full-traits implementation.

In particular, the make_enum factory would probably conflict with declaring enums inside namespaces, and would need some kind of workaround. It's probably solvable with a combination of private data/static methods in objects, and maybe some kind of friend traits class.

Regarding the specific choices:

  1. I (as a matter of preference) decided that fully-explicit, named conversions are "safer" than implicit or explicit casts. I also personally tend to find them much easier to read. Hence the named from_string function, rather than converting constructors.
  2. I also prefer to think of most of my small "objects" as mostly immutable, with mutation explicitly opted into by writing a visible assignment operator. Hence, I would not want from_string to conflate conversion with mutation. Better Enums are supposed to mimic language enums and other basic language types. This actually makes me want to go back and make even more functions static.
  3. from_string should work even if there is no object yet. from_string is logically the enum constructor that converts from strings. It's just not a C++ in-language constructor, but an explicitly-named one obtained by abusing a static function.
  4. Given that there case-sensitive and case-insensitive string conversion functions, it immediately seems odd to me to prefer one to the other for implementing a converting constructor.

It's pretty unfortunate that C++ allows calling from_string as if it were a non-static function. I'd love to disallow that somehow, given the above interpretation.

The stuff above is suited to how I, and some proportion of C++ programmers, think about code. If it hurts you, you're welcome to modify the library. If you think it will be definitively better, a discussion or, even PR, would be quite welcome :)

JaapAap commented 7 years ago

Thanks for that, I think I understand your arguments. Indeed, it would not be much of an issue if the compiler could prevent the static function being called on objects somehow. Just for my understanding, what is the reason for having case sensitive and case insensitive versions of the _from_string factory function? Isn't this dangerous as the _enumeration is in fact case sensitive and could have both "A" and "a" as valid values?

aantron commented 7 years ago

The strongest justification (not that strong) is that the wider project that spawned Better Enums needed both kinds of string conversion. Beyond that reason, it's only that it might be useful for someone else. I suppose at this point, case-insensitive conversion could be removed, if there is suitably large advantage in doing that.

Case-insensitive conversion is slightly dangerous, but I don't think it's particularly dangerous relative to what else can happen with enums. For example, converting from an integer is approximately as dangerous, because two enum constants might have the same numeric value.

aantron commented 7 years ago

Well, it's a different kind of danger. The result of conversion will be correct for the ambiguous from_integer conversion, but potentially incorrect for the ambiguous from_string_nocase conversion. Round-trips can be broken in both cases.