摘要訊息 : C++ 11 之後就應該遺棄弱型別列舉嗎? 列舉還有哪一些不知名的特性?

0. 前言

《C++ 學習筆記》中, 我們已經詳細講述過了列舉, 這篇文章是針對《C++ 學習筆記》中的列舉進行補充. 如果閣下沒有學習過 C++ 中的列舉, 那麼建議先閱讀《C++ 學習筆記》.

更新紀錄 :

  • 2022 年 5 月 16 日進行第一次更新和修正.

1. 再論列舉

C++ 中的列舉分為弱型別和強型別 :

  • 強型別列舉 (enum class) : 要向數值型別進行型別轉化需要借助 static_cast;
  • 弱型別列舉 (enum) : 可以隱含地向數值型別進行轉化, 包括浮點數型別.

在前置宣告的方面, 強型別列舉和弱型別列舉的表現不相同. 強型別列舉支援提前宣告, 但是弱型別列舉不直接支援提前宣告. 在明確標識列舉值的型別之後, 弱型別列舉也可以提前宣告.

C++ 11 引入強型別列舉之後, 是否意味著弱型別列舉應該完全被淘汰呢? 答案顯然是否定的. 根據哲學的說法, 萬物存在皆有理. 首先, 弱型別列舉是從 C 語言遺傳的, 如果取消弱型別列舉, 那麼會導致 C++ 和 C 的直接不相容. 下面再舉一個實例, 說明弱型別列舉在 C++ 11 之後仍然有用 :

#include <tuple>

int main(int argc, char *argv[]) {
    tuple<string /* name */, long /* number */, char /* age */> t {"Jonny", 1, 18};
    enum user_information {
        name, number, age
    };
    auto name = std::get<name>(t);       //獲取名稱, 等價於 std::get<0>(t)
    // ...
}

可以看到, 列舉使得 std::tuple 中存儲的信息更加清晰. 但是如果使用強型別列舉, 那麼我們就需要寫成 auto name = std::get<static_cast<int>(user_information::name)>(t);, 這顯然過於複雜.

另外, 在函式樣板中, 如果強型別列舉作為樣板引數, 它的表現可能不如弱型別列舉要好 :

template <typename E>
void f() {
    E e;
    //...
    type a = static_cast<...>(E::some_value);        // 向本來應有的型別進行轉型, 但是我們不知道列舉值本來持有什麼樣的型別
}

Code 2 中, 如果 E 是弱型別列舉, 那麼我們可以直接把 type 替換為我們想要的型別; 但是如果 E 是強型別列舉, 那麼這個 type 我們沒有辦法去猜測. 這個時候, 我們需要借助來自標頭檔 <type_traits>std::underlying_tpye 來萃取列舉值原本持有的型別 :

#include <type_traits>

template <typename E>
void f() {
    E e;
    //...
    auto a = static_cast<typename std::underlying_type<E>::type>(E::some_value);
}

2. using 與列舉

《C++ 學習筆記》中, 我們沒有提到 using 和列舉的配合用法. 實際上, using 可以提升單個或者多個列舉值的可視範圍 :

#include <iostream>

enum E {
    x = 2020, y, z
};
namespace NS {
    using ::E::x, ::E::y;
}
int main(int argc, char *argv[]) {
    std::cout << NS::x + NS::y << std::endl;        // OK, 輸出 : 2020
}

當然, C++ 20 還引入了直接使用 using enum 來提升列舉值的可視範圍, 這個新特性可以閱讀《【C++ 20】Using Enum》來了解.