Comfortable way to use strong types instead of bools for parameter flags?
Asked Answered
G

0

6

Suppose you have a function that takes a boolean argument:

void do_something(bool use_some_option);

This has issues because on the call site, you see something like do_something(false) which is not very expressive. If you have multiple such options, it gets worse because the parameters could easily be swapped.

Therefore it is often recommended to use strong types instead of bools, for example an enum class. However, you have to define that enum somewhere and it is also quite cumbersome to use an enum class in conditional statements.

My problem with enum class option : bool { no, yes }; is that you cannot use something like if(use_option){...}.

This problem arises quite often in a large codebase, so it would be nice to have a default way to deal with this issue in a convenient way. My current solution looks like this:

template<typename Name>
class Option {
public:
  explicit Option(bool enabled): _enabled(enabled) {}
  operator bool() { return _enabled; }
private:
  bool _enabled;
};

This can now be used like this:

using UseMyOption = Option<struct UseMyOption_>;
void do_something(UseMyOption use_my_option) {
  if(use_my_option) {
    ...
  }
}

Are there any problems with this design for this quite general problem? Are there any issues with an an implicit conversion back to a bool that I am overseeing or is there some further functionality that should be supported?

EDIT: It seems like the code can be replaced by enum UseMyOption : bool {}; Is there any behavioral difference to the above implementation? I also wonder why I have never seen a recommendation to use that pattern before because to me this seems to be very useful. Is there any problem with this?

Grimsley answered 26/8, 2022 at 14:15 Comment(18)
I don't understand the goal here. What is so "cumbersome" about using an enum class? I just don't see the issue. If the goal is to make it more clear to the reader what's going on, why is defining an enum for each "option" is a bad idea?Atalante
The other options is to encapsulate your options in a struct so the members have named fields struct DoSomethingOpts { bool use_option; }; then you could even use designated initializers to do stuff like do_something({.use_option = false});Geodesic
@NicolBolas It is cumbersome to write if(use_option == option::yes) instead of if(use_option).Grimsley
Change enum class option : bool { no, yes }; to enum option : bool { no, yes };. Use namespaces or enclosing struct or class to limit the pollution.Urian
@Urian Ok, that basically does what I wanted (I thought you would also get implicit conversion in the other direction). The minor drawback is that it allows something like do_something(no).Grimsley
yes and no is no more expressive than true and false. It's not a good example. It's more for things like where a bool parameter called is_case_sensitive is less expressive that an enum called case_sensitivity with values case_sensitive and case_insensitive in that of thought if(case_sensitivity) lacks expression and if(case_sensitivity=case_sensitive)` would be preferred anyway.Physicochemical
What about enum is_case_sensitive : bool {};? (I was not aware that this is valid syntax...)Grimsley
@Grimsley That is not a strong type as it allows implicit conversions from bool to the enum type and from the enum type to integer typesFlaw
@Flaw Are you sure? In my tests, I did not perform those implicit conversions from bool to the enum. Only in the other direction.Grimsley
@Grimsley coliru.stacked-crooked.com/a/d5a98404967e8b12Flaw
Yeah, but this is not a conversion from bool to the enum. This is the problematic direction for the application of flags. (and bool converts to int anyway)Grimsley
Design a class which contains your options as bools and significative names then pass this class to your functionMethanol
@MarcoBeninca Often these options are completely indepentent. And it would also be nice to make the approach behave almost identically to bools (just with compiler support and expressiveness) for several reasons (refactoring, less code, easier to write...).Grimsley
@Henk: Even with all this discussion, you've failed to explain what your goal here is. You just declare that you don't like some solution, but you don't explain why. And sometimes, this is contradictory. You claim that true/false are not expressive enough, but you're fine with yes/no. It's unclear what makes for a good solution, since your definition doesn't seem particular firm. Perhaps you could just write the pseudo-code you want to be able to write (both at the call site and test site) and say that you want to do something like that.Atalante
I want a type with that is implicitly covertible to bool, but bool should not implicitly convert to it. And I also want to know, why this should be a bad idea to do when using boolean option parameters. I am happier with yes/no because it is less likely to be used than true and false (and when refactoring, the code fails), but I have also stated that this is a drawback (and the reason why I prefer enum option : bool {};). In the question is an example of pseudo-code what I want to achieve. This is not achievable with enum class.Grimsley
Sometimes, a function that has a bool parameter should simply be split up into two functions. Instead of compare(bool caseSensitive), one could have compare_case_sensitive() and compare_case_insensitive(). If you have several bool parameters, maybe only certain combinations make sense and represent a specific domain concept. Again, separate functions might be better.Armenian
@Armenian I agree. But sometimes there's more than one parameter and combinatoric explosion can lead to a ridiculous number of functions. In many applications which function is selected is a dynamic option so writing separate function entry-points offers the most clarity the code eventually needs some kind of parametric selection function to pick between them at run-time. Think about compare_case_sensitive_ascending(), compare_case_sensitive_descending(), compare_case_insensitive_ascending(), compare_case_insensitive_descending() and so on...Physicochemical
Just because an enumeration only has 2 values does not imply that it is best represented as a bool. One could use a similar argument that if an enumeration has 3 (255?) values, it is ok to be represented as an unsigned char. Strongly typed is strongly typed.Clime

© 2022 - 2024 — McMap. All rights reserved.