Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom string representation #20

Open
martinfinke opened this issue Feb 26, 2019 · 7 comments
Open

Custom string representation #20

martinfinke opened this issue Feb 26, 2019 · 7 comments

Comments

@martinfinke
Copy link

martinfinke commented Feb 26, 2019

Hi, thanks for this very interesting project!

Would it be possible to define a custom string for each enum value, instead of auto-generating it from the token? For example, instead of:

WISE_ENUM(Color, (GREEN, 2), RED)

You could write:

WISE_CUSTOM_ENUM(Color, (GREEN, "vert", 2), (RED, "rouge"))

(there's probably a better name than CUSTOM)

The main advantage for my use case: Let's say you use (GREEN, "GREEN") and the string is persistently stored in configuration files / XML / JSON etc. Later you rename the GREEN enum value in a refactor. This way it remains compatible with the old files because the string "GREEN" is independent. If you want, you can keep the old string, or decide to write custom code for backwards compatibility. But the compatibility doesn't break without notice.

@quicknir
Copy link
Owner

Np :-).

The thing is that probably the single biggest thing that this library achieves is avoiding repeating all the enumerator names. The enumerator name itself doesn't matter much, so usually you just pick something based on how you want it to display (for this reason I'm also a bit confused at the necessity to rename an enum value in a refactor). Once you're writing out all the enumerator strings by hand, couldn't you just write a free function(s) that wrap the to/from string methods of wise enum, and handle any special cases yourself?

There's also a technical reason why this is difficult. As you may know, macro tokenization is only aware of commas and parens. That means that if you have something like (GREEN, foo<3,4>()), the preprocessor actually sees 3 tokens. This means that you can have valid C++ expressions that get turned into multiple preprocessor arguments (which are individually each nonsense). Currently, this is ok because my "tuple" has two arguments, one of which cannot contain commas or any nonsense. So I know that the first entry is the identifier, and I stitch together the rest to make the integer. However, given something like (GREEN, get_identifier<Foo, 0>(), get_value<Foo, 3>()), it's just completely impossible to understand how to process this.

So to support this feature I would start to have all these caveats. Currently, I'm very happy with the fact that I avoid any of these caveats (which people tend to hate about macros); you can use (AFAIK) any valid C++ expression for the enumerator value and it should work just fine.

The closest I could imagine easily at this point is a CUSTOM_ADAPT macro. In that situation, you would declare your enum "unwisely", and then adapt it with custom strings. However, you would still have to repeat all the enumerators themselves, reducing the value. I'm not yet convinced that this is worth it, but I'm happy to hear from you. Or perhaps if you're familiar with the preprocessor, you have some alternate approaches.

@martinfinke
Copy link
Author

Thanks for the extensive reply, that was very insightful 👍 I understand your point, and that what you have now is probably the "sweet spot" of macros.

I'll use a free function layer as you suggested, it's not a big deal. And it's nice that in most cases the code can fall through to the auto-generated strings, without having to type them twice.

@andoks
Copy link

andoks commented Mar 8, 2019

Hi, I just wanted to chime in on this, as this is a use-case I am also looking at.

Once you're writing out all the enumerator strings by hand, couldn't you just write a free function(s) that wrap the to/from string methods of wise enum, and handle any special cases yourself?

The reason is that we really want to have one canonical way of defining these enums. In our case we use them for values that has some hard-coded meaning towards databases. And the database strings sometimes contains characters or combinations thereof, that are not valid in c++ enums. In those cases, we want to specify the enum in c++, and simply add the overriding string representation. If we were to write these by hand, this is a PITA because we then would have to emulate the ergonomics of whatever enum library we use to avoid unpleasant surprises with difference in behaviour (e.g for wise enum, using the custom optional when compiling for c++11, std::optional for c++17, and ALL other behaviour).

We could do what you suggest (which is almost what we are doing now), and then use the enum-library internally in the mapping functions instead, so that those are easy to create in the cases where the strings are legal enum names in c++. We do this currently with better-enums, and then use better-enums make_map functionality for the custom strings.

But all this results in a lot of boilerplate and unpleasantries compared to just being able to use the enum-library without the wrapping on top, if it had some way to customize the enum to string mapping(and back) in the few cases where its needed.

If wise-enum provides this, we would probably switch from better-enums, because the overall ergonomics (with the enums actually being enums) seems better too.

@quicknir quicknir reopened this Mar 11, 2019
@quicknir
Copy link
Owner

@andoks Ok, you raise good points. What would you say to declaring the enum plainly, and then having the adapt macro take optional strings for each enum? You'd have to repeat the enumerators, which is a downside (and keep the enum and adapter in sync). The problem with doing a single macro, as I mentioned before, is the fact that you have three possible arguments for each enumerator: the c++ name, the value, and the string. The latter two are both ideally optional, which I can't really deal with, and worse they're not guaranteed to be single macro tokens. So it's just very difficult to imagine what a single macro would look like. I could have a convenience macro that would allow defining enums with custom strings but not controlling the values, but inevitably someone will ask for that too. And of course, those convenience macros would all have to be multiplied by 4 (for enum/enum class, inside/outside a struct). Another reason why I like the solution of forcing a vanilla enum declaration, and then allowing custom strings in the adapter.

Thoughts?

@andoks
Copy link

andoks commented Mar 12, 2019

@quicknir : for my use-case, the one related to databases and the strings only sometimes not matching what is legal c++ enum names, I have absolutely no issue with declaring it as an enum + some kind of adaptor.

What I think is the most important is not the (relatively) few places we have to define the custom-stringed enums, but rather that the use (to-string, from-string) of the custom-string enums and the not-custom-string enums are identical with respect to the enum-library (wise-enum in this case) API.

If there are differences in the usage API, I foresee huge pains with devs having to learn through painful bugs, that they always have to check the implementation-file for the enum they want to use every time, and check if it is custom-string or not (or automate it by using project-specific functions, but this carries somewhat the same problems, as devs might end up using the enum-API for tostring/fromstring regardless).

@whyman
Copy link

whyman commented Mar 25, 2022

Just a quick note that this would be great for us too, similar use case: Database column names, whilst keeping a consistent code style.

@apmanol
Copy link

apmanol commented Feb 7, 2023

Thanks for this nice software!
The feature described here would be very useful for our case too, because the resulted string doesn't fit C++ constraints

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants