How can I implicitly convert an enum to its subset and vice versa in C++?

120 Views Asked by At

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?

2

There are 2 best solutions below

0
Fareanor On

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.

0
RandomBits On

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);
}