0

More precisely, the feature I want is like implicitly convert an enum to its subset enum and vice versa.

The code I wish it working:

enum class Human {
    A = 1,
    B = 2,
};

enum class Male {    // subset of Human
    A = Human::A,
};

enum class Female {    // subset of Human
    B = Human::B,
};


// some functions can handle all humans
void human_func(Human h) {
    // ...
}

// some only take a subset of humans
void male_func(Male m) {
    // ...
}

void female_func(Female m) {
    // ...
}


// and user only uses values of Human as token
constexpr auto SOMEONE = Human::A;

int main() {
    human_func(SOMEONE);  // ok
    male_func(SOMEONE);   // also ok, Human::A implicitly converted to Male
    female_func(SOMEONE); // failed, can't convert Human::A to Female.
}

But enum can not do the conversion. Now I have two options:


// 1. static_assert with template parameter

template <Human H>
void female_func() {
    static_assert(H == Human::B);
    // ...
}

// 2. manually convert it

#define _ENUM_TO_ENUM(e1, e2) \
    static_cast<e2>(static_cast<std::underlying_type_t<decltype(e1)>>(e1))

void female_func(_ENUM_TO_ENUM(SOMEONE, Female)) {
    // But this way the compiler does not check if the value is valid.
    // I can put anything in.
    // ...
}

So is there other techniques to accomplish this?

digito_evo
  • 3,216
  • 2
  • 14
  • 42
ke_bitter
  • 9
  • 1
  • Implicit conversions may seem convenient to safe a little bit of code typing, howver implicit conversion is best avoided if you want your code to be maintainable. For example : watch [C++ weekly, implicit casts are evil](https://www.youtube.com/watch?v=T97QJ0KBaBU). So it is best not to learn how to do it, but how to avoid it. Like always mark you constructors with a single argument as `explicit`. Explicitly written code is easier to understand later when you need to maintain/debug and/or test your code. – Pepijn Kramer Jun 02 '23 at 12:17
  • 4
    This feels very much like an [XY problem](https://en.wikipedia.org/wiki/XY_problem)... Why do you need "subsets" of enumerations? What is the original, actual and underlying problem that's supposed to solve? Perhaps there are other ways to solve that original problem? Perhaps you can use templates and specialization instead? And if you have a function named like `female_func`, why does it even take an argument if we already know it's for "females" by just reading its name? – Some programmer dude Jun 02 '23 at 12:21
  • 2
    Restating something almost verbatim is in no way "more precise". – Scott Hunter Jun 02 '23 at 12:21
  • It seems what you are looking for is polymorphism. – Fareanor Jun 02 '23 at 12:28
  • I think something like [this](https://godbolt.org/z/chdrjqYq3) is more suited to your need. – Fareanor Jun 02 '23 at 12:41
  • Notice that values inside range of underlying type of enum are valid (even if not listed in definition). – Jarod42 Jun 02 '23 at 12:41
  • *So is there other techniques to accomplish this?* **No**, there is no way to accomplish *implicit* conversions here. You need to use *explicit* conversions. – Eljay Jun 02 '23 at 12:48
  • 2
    *"its subset enum"* -- please be aware that this is a concept that you invented; it is not something the C++ language -- or even other programmers -- are aware of. You might want to give a precise, concrete definition of what you mean, rather than trying to define through examples. – JaMiT Jun 02 '23 at 12:50

2 Answers2

2

I think this is the typical use case for inheritance and polymorphism.

If you consider to make the Man and Woman enumerations to be classes instead, both deriving for a polymorphic Human class, and only keeping an enumeration for the gender, the code you "wish to be working" would work for sure.

Here is an example of the required rework:

enum class Gender {MALE, FEMALE};

struct Human
{
    Gender m_gender;
    Human(Gender g) : m_gender{g}
    {}
    virtual ~Human() = default;
};

struct Man : public Human
{
    Man() : Human{Gender::MALE}
    {}
};
struct Woman : public Human
{
    Woman() : Human(Gender::FEMALE)
    {}
};

Now you can just write your functions as follows:

void human_func(const Human & h)
{
    //...
}
void man_func(const Man & m)
{
    //...
}
void woman_func(const Woman & w)
{
    //...
}

And use them the following way:

int main()
{
    Man man;

    human_func(man);  // OK --> Human
    man_func(man);    // OK --> Man
    //woman_func(man);   NOK (invalid: a Man is not a Woman)

    return 0;
}

The reason why it works is that with inheritance, a Man is a Human. The same goes for a Woman.

Fareanor
  • 5,900
  • 2
  • 11
  • 37
1

An enum class in C++ cannot have any type of is a relationship with another enum class at least at the language level.

You may be able to achieve what you want using a standard type hierarchy such that Male is a Human and Female is a Human as demonstrated by the sample code. Although, this may be the inverse of what you are looking for.

Sample Code

#include <iostream>

using std::cin, std::cout, std::endl;

struct Human {
};

struct Male : public Human {
};

struct Female : public Human {
};

// some functions can handle all humans
void human_func(Human& h) {
    // ...
}

// some only take a subset of humans
void male_func(Male& m) {
    // ...
}

void female_func(Female& m) {
    // ...
}

// and user only uses values of Human as token

int main() {
    auto human = Human{};
    human_func(human);
    // male_func(human); This would not compile
    // female_func(human); This would not compile

    auto male = Male{};
    human_func(male);
    male_func(male);
    // female_func(male); This would not compile

    auto female = Female{};
    human_func(female);
    // male_func(female); This would not compile
    female_func(female);
}
RandomBits
  • 4,194
  • 1
  • 17
  • 30