という問題。名前被り等を気にする必要はなくなったが、 整数型への暗黙の変換も、enum 同士の計算もなにかしらを実装しなければならなくなった。 多くのひとがこれは使いにくいと思っているようで、
計算系は偉大な先輩方が強力なものを作ってくれている、が、見習い魔術師には高級言語過ぎて理解できなかったので 自分でも書いてみた。
ソース全文。ひとまず論理和だけ実装しているのだが・・・長い。
#include <iostream>
#include <type_traits>
template < typename T >
struct enum_cast {
private:
static_assert(
std :: is_enum< T > :: value
, "enum_cast only used to enum type"
);
public:
using type = T;
using underlying_type = typename std :: underlying_type< T > :: type ;
static constexpr underlying_type
to_underlying ( const type t ) { return static_cast< underlying_type >( t ) ; }
static constexpr type
to_enum ( const underlying_type t ) { return static_cast< type >( t ) ; }
};
template < typename T, typename U >
using is_same_underlying = std :: is_same<
typename enum_cast< T > :: underlying_type
, typename enum_cast< U > :: underlying_type
>;
template < typename... Args>
class is_enum_logic_safe {
private:
// no args
template < typename... InArgs >
struct impl : std :: false_type {};
// single arg
template < typename Type1, typename... Tail >
struct impl< Type1, Tail... > : std :: true_type {};
// more than 2 args
template < typename Type1, typename Type2, typename... Tail >
struct impl< Type1, Type2, Tail... > :
std :: conditional<
is_same_underlying< Type1, Type2 > :: value
, typename impl< Type2, Tail... > :: type
, std :: false_type
> :: type
{};
public:
static constexpr bool value = impl< Args... > :: type :: value;
};
template < typename T, typename U = T >
struct enum_logic_enabled : std :: false_type {};
template < typename T, typename U >
using enum_enabler = std :: enable_if<
enum_logic_enabled< T, U > :: value &&
is_enum_logic_safe< T, U > :: value
, std :: nullptr_t
>;
template <
typename T
, typename U = T
, typename enum_enabler< T, U > :: type = nullptr
>
static constexpr T operator| ( T t, U u ) {
return enum_cast< T > :: to_enum(
enum_cast< T > :: to_underlying( t ) | enum_cast< U > :: to_underlying( u )
) ;
}
enum class A : unsigned char {
one
};
enum class B : unsigned char {
two
};
enum class C : unsigned int {
three
};
template <>
struct enum_logic_enabled< A > : std :: true_type {};
template <>
struct enum_logic_enabled< C > : std :: true_type {};
template <>
struct enum_logic_enabled< A, C > : std :: true_type {};
template <>
struct enum_logic_enabled< A, B > : std :: true_type {};
int main(int argc, const char * argv[]) {
A a;
B b;
C c;
a = A :: one | A :: one;
//b = B :: two | B :: two;
c = C :: three | C :: three;
a = A :: one | B :: two ;
//a = A :: one | C :: three;
unsigned char uc = 0;
int i = 0;
//enum_cast< int > :: type j = 0;
return 0;
}
template < typename T >
struct enum_cast {
private:
static_assert( std :: is_enum< T > :: value , "enum_cast only used to enum type" );
public:
using type = T;
using underlying_type = typename std :: underlying_type< T > :: type ;
static constexpr underlying_type
to_underlying ( const type t ) { return static_cast< underlying_type >( t ) ; }
static constexpr type
to_enum ( const underlying_type t ) { return static_cast< type >( t ) ; }
};
enum_cast<T> :: type
enum_cast<T> :: underlying_type
enum_cast<T> :: to_underlying(const type t)
enum_cast<T> :: to_enum(const underlying_type t)
他に
private:
static_assert( std :: is_enum< T > :: value , "enum_cast only used to enum type" );
T が enum class でない場合、コンパイル時に enum_cast only used to enum type
とエラーを吐いて終了する。
static constexpr T operator| ( T t, U u ) {
return enum_cast< T > :: to_enum( //enum class 型に戻す・・・(2)
enum_cast< T > :: to_underlying( t ) | enum_cast< U > :: to_underlying( u ) //enum classの基底型にして計算・・・(1)
) ;
このように、演算を行う際はこの2つを組み合わせて利用する。
template < typename T, typename U >
using is_same_underlying = std :: is_same<
typename enum_cast< T > :: underlying_type
, typename enum_cast< U > :: underlying_type
>;
2つの enum class を受け取って基底型が同一か確認する。
基底型の取得には enum_cast<T> :: underlying_type
を利用している。
なので、T
/ U
が enum class でない場合、エラーとなる。
今回2つの enum class 同士の計算ができるように作っているが、それは
enum class status : unsigned char{
off = 0b00000000
, standby = 0b00000001
, running = 0b00000010
, abort = 0b00000011
, overflow = 0b10000000
};
enum class mask : unsigned char {
overflow = 0b10000000
, status = 0b00000011
};
このような enum class 同士の計算を想定している。
template < typename... Args>
class is_enum_logic_safe {
private:
// no args
template < typename... InArgs >
struct impl : std :: false_type {};
// single arg
template < typename Type1, typename... Tail >
struct impl< Type1, Tail... > : std :: true_type {};
// more than 2 args
template < typename Type1, typename Type2, typename... Tail >
struct impl< Type1, Type2, Tail... > :
std :: conditional<
is_same_underlying< Type1, Type2 > :: value
, typename impl< Type2, Tail... > :: type
, std :: false_type
> :: type
{};
public:
static constexpr bool value = impl< Args... > :: type :: value;
};
与えられたテンプレート引数同士で計算して問題がないかチェックする。
// no args
template < typename... InArgs >
struct impl : std :: false_type {};
引数が何もない状態では計算も何もないので std :: false_type
// more than 2 args
template < typename Type1, typename Type2, typename... Tail >
struct impl< Type1, Type2, Tail... > :
std :: conditional<
is_same_underlying< Type1, Type2 > :: value
, typename impl< Type2, Tail... > :: type
, std :: false_type
> :: type
{};
引数が2つ以上の場合に呼ばれる。 先頭から順番に enum class の基底型が同一かをチェックする動作を実装した。
1つ目と2つ目、 enum class Type1
/ Type2
の基底型が同一だった場合、
Type2
を先頭にしたパラメータパックでもう一度 impl(...)
を呼び出す、
という処理が引数1つになるまで続く。
// single arg
template < typename Type1, typename... Tail >
struct impl< Type1, Tail... > : std :: true_type {};
std :: true_type
Type1
= 前ループのType2
、
つまり最後の引数まで全て基底型が同一だったことになるので
std :: true_type
を返してあげなければならないpublic:
static constexpr bool value = impl< Args... > :: type :: value;
処理結果を value
として定義。これが返り値になる。
template < typename T, typename U >
using enum_enabler = std :: enable_if<
enum_logic_enabled< T, U > :: value &&
is_enum_logic_safe< T, U > :: value
, std :: nullptr_t
>;
各種 operator
の第3引数に指定され、
template < typename T , typename U = T, typename enum_enabler< T, U > :: type = nullptr >
関数の有効/無効を制御する。 std :: enable_if
は
std :: enable_if<
/* 条件 */
/* trueの時の型 */
> :: type /* trueの場合のみ type が存在する */
というテンプレートメタ関数。なので
enum class T
/ U
間の計算を有効化(enum_logic_enabled< T, U > :: value
) されており(後述)、かつ
enum class T
/ U
間で計算して問題ない(is_enum_logic_safe< T, U > :: value
)時に限り、
template < typename T , typename U = T, typename std :: nullptr_t = nullptr >
となり関数が有効化する。もし条件に合わない場合、
template < typename T , typename U = T>
の探索が始まるが、定義されていない場合はエラーとなる。
typename U = T
テンプレート引数が1つだけの場合、U
には T
が代入されるので、T
同士の計算となる。
template < typename T, typename U = T >
struct enum_logic_enabled : std :: false_type {};
enum class 型 T
と U
間の計算の有効/無効を定義する。タグディスパッチと呼ばれる手法。
有効化したい組み合わせを
template <>
struct enum_logic_enabled< A > : std :: true_type {};
template <>
struct enum_logic_enabled< C > : std :: true_type {};
template <>
struct enum_logic_enabled< A, C > : std :: true_type {};
template <>
struct enum_logic_enabled< A, B > : std :: true_type {};
このように std :: true_type
を継承させて特殊化させることで、
上記 enum_enabler
の条件式が変化する。
ただし、有効化させても計算に問題がある( is_enum_logic_safe<T, U> :: value = false
) 場合、
関数は有効にならない。
template < typename T , typename U = T, typename enum_enabler< T, U > :: type = nullptr >
static constexpr T operator| ( T t, U u ) {
return enum_cast< T > :: to_enum(
enum_cast< T > :: to_underlying( t ) | enum_cast< U > :: to_underlying( u )
) ;
}
ところどころバラバラになって出てきたがこのように書く。
他の演算子を追加するには、例えば &
を追加する場合、
template < typename T , typename U = T, typename enum_enabler< T, U > :: type = nullptr >
static constexpr T operator& ( T t, U u ) {
return enum_cast< T > :: to_enum(
enum_cast< T > :: to_underlying( t ) & enum_cast< U > :: to_underlying( u )
) ;
}
このようにするだけで有効化している組み合わせ全てで operator&
の計算が有効化される。
テストケースの enum class
enum class A : unsigned char {
one
};
enum class B : unsigned char {
two
};
enum class C : unsigned int {
three
};
main 関数内
A a;
B b;
C c;
a = A :: one | A :: one;
//b = B :: two | B :: two;
//Bの計算が有効化されていないためエラー
c = C :: three | C :: three;
a = A :: one | B :: two ;
//a = A :: one | C :: three;
//A - C 間で計算は有効化されているが基底型が違うのでエラー
unsigned char uc = 0;
int i = 0;
//enum_cast< int > :: type j = 0;
//int は enum class ではないのでエラー
なし
namespace
に入れるなどは各自で。