文字列と相互変換できる列挙型(enum)
stringとenumを双方向で変換できてかつPODなのが欲しくなったので書きました。内部の変換テーブルにはboost::bimapを使っています。
特徴としては
- 双方向に変換できる
- 実行時
- POD型
- boost::bimapなので速い(後述)
などです。
使用例
ENUM_UTILの実装は(長いので)記事の最後にあります。
#include <iostream> #include "EnumUtil.h" using namespace std; //文字列変換できるenum ENUM_UTIL(Number, ZERO, ONE, THREE = 3, ); //クラス内にも定義可能 class MyClass { ENUM_UTIL(Fruit, APPLE, BANANA, ORANGE); }; int main() { //文字列との相互変換 Number zero = Number("ZERO"); cout << to_string(zero) << endl; cout << to_string(Number::ONE) << " " << to_string(Number(Number::ONE)) << endl; //入出力 Number n; cin >> n; cout << n << endl; //intとの変換 int one = Number::ONE; Number three = static_cast<Number>(3); //比較 assert(Number::ZERO == 0); assert(Number::ZERO < Number::ONE and Number::ONE < Number::THREE); //switch文 switch (zero) { case Number::ZERO: break; default: assert(false); } }
実行結果
ZERO
1 ONE
THREE //input
THREE
速度
(主にbimapの)速度が不明だったので、要素が3つのenumを定義して列挙型→文字列→列挙型の変換を10億回繰り返してみました。
また、比較対象として
- 列挙型→文字列はswitch、文字列→列挙型はifで分岐して変換
- unordered_mapを2つ使って変換
も同時に計測しました。環境はgcc 5.1.0でオプションは-O3 -march=nativeです。
実装
EnumUtil.h
#ifndef ENUMUTIL_H_ #define ENUMUTIL_H_ #include <type_traits> #include <string> #include <boost/algorithm/string.hpp> #include <boost/bimap/bimap.hpp> #include <boost/bimap/unordered_set_of.hpp> namespace detail { template <class T> void initRegistry(boost::bimaps::bimap<boost::bimaps::unordered_set_of<T>, boost::bimaps::unordered_set_of<std::string>>& registry, std::string args) { //,で分解する std::vector<std::string> lines; boost::algorithm::split(lines, args, boost::is_any_of(",")); T value = 0; for (std::string& line : lines) { boost::algorithm::trim(line); if (line.size() == 0) { continue; } //=で分解を試みる std::vector<std::string> terms; boost::algorithm::split(terms, line, boost::is_any_of(" ="), boost::algorithm::token_compress_on); if (terms.size() >= 2) { //分解できれば値を更新 value = atoi(terms[1].c_str()); } //ペアを追加 registry.insert(typename boost::bimaps::bimap<boost::bimaps::unordered_set_of<T>, boost::bimaps::unordered_set_of<std::string>>::value_type(value, terms[0])); ++value; } } //SFINAE用の基底クラス struct EnumUtilBase {}; //入出力対応 template <class T, class = typename std::enable_if<std::is_base_of<EnumUtilBase, T>::value>::type> std::string to_string(T t) { return t.to_string(); } template <class T, class = typename std::enable_if<std::is_base_of<EnumUtilBase, T>::value>::type> std::istream& operator>>(std::istream& is, T& rhs) { std::string buf; is >> buf; rhs = T(buf); return is; } template <class T, class = typename std::enable_if<std::is_base_of<EnumUtilBase, T>::value>::type> std::ostream& operator<<(std::ostream& os, T rhs) { os << rhs.to_string(); return os; } } //文字列変換付きenum #define ENUM_UTIL(Name, ...)\ class Name : detail::EnumUtilBase {\ public:\ enum Enum {\ __VA_ARGS__\ };\ Name() = default;\ Name(Enum value) {\ enumHolder.value = value;\ }\ explicit Name(std::underlying_type<Enum>::type value) {\ enumHolder.value = static_cast<Enum>(value);\ }\ explicit Name(const std::string& str) {\ enumHolder.value = static_cast<Enum>(getRegistry().right.at(str));\ }\ operator Enum() const {\ return enumHolder.value;\ }\ std::string to_string() const {\ return getRegistry().left.at(enumHolder.value);\ }\ private:\ typedef boost::bimaps::bimap<boost::bimaps::unordered_set_of<std::underlying_type<Enum>::type>, boost::bimaps::unordered_set_of<std::string>> registry_type;\ struct RegistryHolder {\ RegistryHolder() {\ detail::initRegistry(registry, #__VA_ARGS__);\ }\ registry_type registry;\ };\ static registry_type& getRegistry() {\ static RegistryHolder registryHolder;\ return registryHolder.registry;\ }\ struct EnumHolder {\ Enum value;\ } enumHolder;\ };\ static_assert(std::is_pod<Name>(), "ENUM_UTIL should be POD"); #endif /* ENUMUTIL_H_ */