如何在 C++ 中将枚举隐式转换为其子集,反之亦然?

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

提问人:ke_bitter 提问时间:6/2/2023 最后编辑:digito_evoke_bitter 更新时间:6/3/2023 访问量:106

问:

更准确地说,我想要的功能就像将枚举隐式转换为其子集枚举,反之亦然。

我希望它工作的代码:

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.
}

但是枚举不能进行转换。现在我有两个选择:


// 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.
    // ...
}

那么还有其他技术可以做到这一点吗?

C++ 隐式转换 枚举类

评论

0赞 Pepijn Kramer 6/2/2023
隐式转换似乎很方便,可以保护一点代码类型,如果您希望代码可维护,最好避免隐式转换。例如:每周观看 C++,隐式强制转换是邪恶的。所以最好不要学习如何去做,而是如何避免它。就像总是用单个参数将构造函数标记为 .显式编写的代码在以后需要维护/调试和/或测试代码时更容易理解。explicit
4赞 Some programmer dude 6/2/2023
这感觉很像一个 XY 问题......为什么需要枚举的“子集”?应该解决的原始、实际和潜在的问题是什么?也许还有其他方法可以解决这个原始问题?也许您可以使用模板和专业化来代替?如果你有一个名为 like 的函数,如果我们已经通过阅读它的名字就知道它是针对“女性”的,为什么还要进行论证呢?female_func
2赞 Scott Hunter 6/2/2023
几乎逐字逐句地重述某些内容绝不是“更精确”。
0赞 Fareanor 6/2/2023
看来你要找的是多态性。
2赞 JaMiT 6/2/2023
“它的子集枚举”——请注意,这是你发明的概念;这不是C++语言 - 甚至其他程序员 - 意识到的事情。您可能希望对您的意思给出一个准确、具体的定义,而不是试图通过示例来定义。

答:

2赞 Fareanor 6/2/2023 #1

我认为这是继承和多态性的典型用例。

如果您考虑将 和 枚举改为类,既派生多态类,又只保留性别的枚举,那么您“希望工作”的代码肯定会起作用。ManWomanHuman

以下是所需返工的示例:

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

现在,您可以按如下方式编写函数:

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

并按以下方式使用它们:

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

它之所以有效,是因为对于继承,a ..ManHumanWoman

1赞 RandomBits 6/2/2023 #2

至少在语言层面上,一个 in 不能与另一个 in 有任何类型的关系。enum classC++is aenum class

您可以使用标准类型层次结构实现所需的内容,如示例代码所示。虽然,这可能与您正在寻找的相反。Maleis aHumanFemaleis aHuman

示例代码

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