ビットフラグとsocped enumあたりを調べたら、
- C++のscoped enumで関数のフラグ指定をしたい by @prickle
- 君の名は・・・enum class by @yumetodo
- Re2: C++のscoped enumで関数のフラグ指定をしたい & 君の名は・・・enum class by @akinomyoga
というのを見かけて、自分なりに作りたくなったので作りました。
今回、説明はかなりザックリです。
(2018/08/04追記)
さらなる別解が投稿されてたのでここでも紹介します。
方針
今回はできるだけシンプルにしたいので1つのenumを対象にします(2つ以上のenumを受け入れるようにするのが面倒なので)。
上記の記事で特定の構造体を特殊化して演算子を使えるようにする時、std::true_type
を継承する必要があったので、継承する必要がないようにします。つまり、以下のようにenable_bit_flags_operators
を特殊化することで演算子が使えるようにします。
// 例えばこういうビットフラグのenumを定義
enum struct flags_t
{
a = 1,
b = 1 << 1,
};
// flags_tについて演算子が使えるように有効化する
namespace spirea {
template <>
struct enable_bit_flags_operators< flags_t >
{ };
}
// ビット演算子が使えるようになる
flags_t v = flags_t::a | flags_t::b
こんな感じで書けるようにしたいと思います。
ちなみに、spirea
は適当に作ってる俺々ライブラリの名前です。
コード
こうなりました。一応解説は下の方に。
#ifndef SPIREA_BIT_FLAGS_HPP_
#define SPIREA_BIT_FLAGS_HPP_
#include <type_traits>
namespace spirea {
template <typename T>
struct enable_bit_flags_operators;
template <typename T, typename = void>
struct is_enabled_bit_flags_operators :
public std::false_type
{ };
template <typename T>
struct is_enabled_bit_flags_operators< T, std::void_t< decltype( enable_bit_flags_operators< T >() ) > > :
public std::true_type
{
static_assert( std::is_enum_v< T > );
};
template <typename T>
constexpr bool is_enabled_bit_flags_operators_v = is_enabled_bit_flags_operators< T >::value;
namespace detail {
template <typename T>
constexpr auto operator~(T x) noexcept
-> std::enable_if_t< is_enabled_bit_flags_operators_v< T >, T >
{
return static_cast< T >( ~static_cast< std::underlying_type_t< T > >( x ) );
}
template <typename T>
constexpr auto operator&(T lhs, T rhs) noexcept
-> std::enable_if_t< is_enabled_bit_flags_operators_v< T >, T >
{
using underlying = std::underlying_type_t< T >;
return static_cast< T >( static_cast< underlying >( lhs ) & static_cast< underlying >( rhs ) );
}
template <typename T>
constexpr auto operator|(T lhs, T rhs) noexcept
-> std::enable_if_t< is_enabled_bit_flags_operators_v< T >, T >
{
using underlying = std::underlying_type_t< T >;
return static_cast< T >( static_cast< underlying >( lhs ) | static_cast< underlying >( rhs ) );
}
template <typename T>
constexpr auto operator^(T lhs, T rhs) noexcept
-> std::enable_if_t< is_enabled_bit_flags_operators_v< T >, T >
{
using underlying = std::underlying_type_t< T >;
return static_cast< T >( static_cast< underlying >( lhs ) ^ static_cast< underlying >( rhs ) );
}
template <typename T>
constexpr auto operator&=(T& lhs, T rhs) noexcept
-> std::enable_if_t< is_enabled_bit_flags_operators_v< T >, T& >
{
return lhs = lhs & rhs;
}
template <typename T>
constexpr auto operator|=(T& lhs, T rhs) noexcept
-> std::enable_if_t< is_enabled_bit_flags_operators_v< T >, T& >
{
return lhs = lhs | rhs;
}
template <typename T>
constexpr auto operator^=(T& lhs, T rhs) noexcept
-> std::enable_if_t< is_enabled_bit_flags_operators_v< T >, T& >
{
return lhs = lhs ^ rhs;
}
} // namespace detail
} // namespace spirea
using spirea::detail::operator~;
using spirea::detail::operator&;
using spirea::detail::operator|;
using spirea::detail::operator^;
using spirea::detail::operator&=;
using spirea::detail::operator|=;
using spirea::detail::operator^=;
namespace spirea {
template <typename T>
constexpr auto enabled(T src, T v) noexcept
-> std::enable_if_t< is_enabled_bit_flags_operators_v< T >, bool >
{
return ( src & v ) == v;
}
template <typename T>
constexpr auto disabled(T src, T v) noexcept
-> std::enable_if_t< is_enabled_bit_flags_operators_v< T >, bool >
{
return !enabled( src, v );
}
} // namespace spirea
#endif // SPIREA_BIT_FLAGS_HPP_
解説
is_enabled_bit_flags_operators
を使ったオーバーロード
重要なのはis_enabled_bit_flags_operators
メタ関数です。これはenable_bit_flags_operators
がT
で特殊化されている場合std::true_type
、いない場合をstd::false_type
で返すものです。以下にこのメタ関数を抜粋します。
template <typename T, typename = void>
struct is_enabled_bit_flags_operators :
public std::false_type
{ };
template <typename T>
struct is_enabled_bit_flags_operators< T, std::void_t< decltype( enable_bit_flags_operators< T >() ) > > :
public std::true_type
{
static_assert( std::is_enum_v< T > );
};
template <typename T>
constexpr bool is_enabled_bit_flags_operators_v = is_enabled_bit_flags_operators< T >::value;
実装としては、is_enabled_bit_flags_operators
を特殊化でenable_bit_flags_operators
のオブジェクトを作ってみてSFINAEで場合分けをしています。
ちなみにstd::void_t
はC++17で追加されたもので、何をいくつテンプレート引数に入れてもvoidを返すメタ関数です。上記のように特殊化でSFINAEするときに便利です。
std::void_t - cppreference.com
http://en.cppreference.com/w/cpp/types/void_t
構造体のis_enabled_bit_flags_operators
はそのままだと使いにくいのでis_enabled_bit_flags_operators_v
を書いて楽をします。あとは、is_enabled_bit_flags_operators
を使って演算子をstd::enable_if_t
でオーバーロードしていくだけです。以下はoperator|
を抜粋したものです。
template <typename T>
constexpr auto operator|(T lhs, T rhs) noexcept
-> std::enable_if_t< is_enabled_bit_flags_operators_v< T >, T >
{
using underlying = std::underlying_type_t< T >;
return static_cast< T >( static_cast< underlying >( lhs ) | static_cast< underlying >( rhs ) );
}
返り値の型を後置にしているのはただの趣味です。
enabled関数、disabled関数
フラグが立っているか判定するのに( src & v ) == v
のv
みたいに同じフラグを2回書くのが面倒だなーと思ったので作った関数です。特に説明いらないですよね。
template <typename T>
constexpr auto enabled(T src, T v) noexcept
-> std::enable_if_t< is_enabled_bit_flags_operators_v< T >, bool >
{
return ( src & v ) == v;
}
template <typename T>
constexpr auto disabled(T src, T v) noexcept
-> std::enable_if_t< is_enabled_bit_flags_operators_v< T >, bool >
{
return !enabled( src, v );
}
おわりに
こういう例もあるよってことで1つ。
参考
std::void_t - cppreference.com
http://en.cppreference.com/w/cpp/types/void_t
spirea/bit_flags.hpp
https://github.com/LNSEAB/spirea/blob/master/include/spirea/bit_flags.hpp