自分なりのscoped enumビットフラグヘルパー

ビットフラグとsocped enumあたりを調べたら、

というのを見かけて、自分なりに作りたくなったので作りました。
今回、説明はかなりザックリです。

方針

 今回はできるだけシンプルにしたいので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は適当に作ってる俺々ライブラリの名前です。

コード

こうなりました。一応解説は下の方に。

bit_flags.hpp
#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_operatorsTで特殊化されている場合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 ) == vvみたいに同じフラグを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

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.