1

これが私のソース(回答として)、underlying_value、およびto_enum関数の実装方法です。 強く型付けされた列挙型を int に自動的に変換する方法は?

基本値 - 問題ありません。しかし、to_enum - 問題があります。

見る:

enum class E{ a = 1, b = 3, c = 5 };
auto e_a = utils::underlying_value(E::a); //OK
E t = utils::to_enum<E>( 2 ) ; // compiled, but it's incorrect. I think here must throws exception?

Q: to_enum を正しく実装するには?

4

1 に答える 1

1

質問に対するコメントにもかかわらず、これは C++11 で実行できますが、コードを繰り返さずに実行するには、最終的にenum class宣言をマクロでラップする必要があります。あなたのニーズによっては、私の答えが不適切になるかもしれません。いずれにせよ、チェック付き変換を行うにはいくつかの機械が必要なので、最後にマクロに取り掛かります。

基本的な考え方は、constexpr関数を使用して配列をスキャンすることです。

#include <iostream>
#include <stdexcept>

enum class E { a = 1, b = 3, c = 5 };

constexpr E         values[] = {E::a, E::b, E::c};
constexpr size_t    count = sizeof(values) / sizeof(E);

constexpr E to_enum(int value, size_t index = 0)
{
    return
        index >= count ? throw std::runtime_error("invalid integer") :
        static_cast<int>(values[index]) == value ? values[index] :
        to_enum(value, index + 1);
}

constexpr E         converted = to_enum(3);

// Will not compile if uncommented.
// constexpr E         bad_converted = to_enum(2);

int main()
{
    std::cout << static_cast<int>(converted) << std::endl;

    return 0;
}

これは印刷され3ます。の行bad_convertedがコメント解除されている場合、このコードはまったくコンパイルされません。チェック済み変換は、実行時またはコンパイル時に実行できます。引数 toto_enumがコンパイル時の定数である場合、コンパイル中に実行されます。また、おそらくご覧のとおり、これは の線形スキャンをvalues行いますが、非常に大きな列挙型のパフォーマンスの問題になる場合は、別のアルゴリズムに置き換えることができます。


先ほど示したコードは、基になるメソッドを示すスケッチです。これを簡単に使用できるようにするには、配列と関連する関数Eを自動的に生成するマクロでの宣言をラップする必要があります。values[]このマクロの内容を 1 点ずつ示して説明します。

基本マクロはこんな感じ

// Declaration header
#define ENUM_CLASS(TypeName, __VA_ARGS__)

// Use
ENUM_CLASS(E, a = 1, b = 3, c = 5);

したがって、この例で__VA_ARGS__は tokens になりa = 1, b = 3, c = 5ます。したがって、次のようにマクロ内で enum 自体を宣言できます。

enum class TypeName { __VA_ARGS__ };

ただし、次のように単純に宣言することはできません。

constexpr TypeName values[] = { __VA_ARGS__ };

に展開するからです。

constexpr TypeName values[] = { a = 1, b = 3, c = 5 };

これはスコープが設定されておらず (TypeName::各値の前にありません)、配列初期化子内に余分な代入演算子があるため、有効な C++ ではありません。最初に 2 番目の問題を解決します。次のようなクラスを定義する必要があります。

template <typename E>
class swallow_assignment {
  public:
    E _value;

    constexpr explicit swallow_assignment(E value) : _value(value)
    {
    }

    template <typename Any>
    constexpr const swallow_assignment& operator =(Any other) const
    {
        return *this;
    }

    constexpr operator E() const
    {
        return _value;
    }
};

これで、書くことができます(swallow_assignment<E>)E::a = 1。何が起こるかというと、コンパイル時に、と同じ内部表現を持つE::a割り当て可能な値に変換されます。その値は の代入を無視し、に変換されます。(swallow_assignment<E>)E::aE::a1E::a


残っているのは、宣言された定数のそれぞれにプレフィックスを付けて、取得できるようにすることです

constexpr TypeName values[] =
    {(swallow_assignment<E>)E::a = 1,
     (swallow_assignment<E>)E::b = 3,
     (swallow_assignment<E>)E::c = 5})

これは有効な初期化子になります。これは、マッピング マクロを使用して行うことができます。これはまったく別のトピックなので、ここでは詳しく説明しませんが、そのようなマクロはhttps://github.com/aantron/better-enums/blob/e28177b11a9e3d7152c5216d84fdf8939aff0eba/enum_preprocessor_map.hにあります。ブーストには、より良いものがあるかもしれません。どのようなマクロを使用していても、その署名はPP_MAP(prefix, __VA_ARGS__). 列挙型全体の最終的なマクロ定義のスケッチは次のようになります。

#define ENUM_CLASS(TypeName, __VA_ARGS__)                  \
    enum class TypeName { __VA_ARGS__ };                   \
    constexpr TypeName values[] =                          \
        { PP_MAP((swallow_assignment<TypeName>)TypeName::, \
                 __VA_ARGS__) };                           \
    constexpr size_t count = sizeof(values) / sizeof(TypeName);

おそらく、これらの定義を特性タイプの特殊化に詰め込み、このマクロを複数で使用できるようにすることをお勧めしますenum class(そうしないと、指定された配列valuesが衝突します)。valuesただし、特性クラスの静的メンバーを作成する場合は、リンクの問題を回避するために弱いシンボルを使用する必要がある場合があります。

この答えはすでに長すぎるため、これらの最後のポイントは演習として残されています:)上記のすべてを実行するライブラリがありますが、enumの特性特殊化を提供する代わりに をラップしenum classます。enum classただし、/traits を組み合わせた未公開のブランチがあります。ライブラリはhttp://aantron.github.io/better-enumsで確認できます。ライブラリの::_from_integral()メソッドはto_enum質問の関数に対応し、実行時とコンパイル時の両方の変換を行います。

于 2015-05-15T17:16:58.960 に答える