Как использовать класс enum как набор флагов?

Скажем, у меня есть набор флагов и класс вроде этого:

/// <summary>Options controlling a search for files.</summary>
enum class FindFilesOptions : unsigned char
{
    LocalSearch = 0,
    RecursiveSearch = 1,
    IncludeDotDirectories = 2
};

class FindFiles : boost::noncopyable
{
    /* omitted */
public:
    FindFiles(std::wstring const& pattern, FindFilesOptions options);
    /* omitted */
}

и я хочу, чтобы вызывающий абонент мог выбрать несколько вариантов:

FindFiles handle(Append(basicRootPath, L"*"),
    FindFilesOptions::RecursiveSearch | FindFilesOptions::IncludeDotDirectories);

Можно ли это поддерживать строго типизированным способом с С++ 11 enum class, или мне нужно вернуться к нетипизированным перечислениям?

(Я знаю, что вызывающий может static_cast для базового типа и static_cast назад, но я не хочу, чтобы вызывающий абонент должен был это делать)

Ответ 1

Конечно, можно использовать enum class es для растровых изображений. К сожалению, это немного больно: вам нужно определить необходимые битовые операции для вашего типа. Ниже приведен пример того, как это может выглядеть. Было бы неплохо, если бы enum class es мог получить какой-то другой тип, который мог бы жить в подходящем пространстве имен, определяющем необходимый код шаблона оператора.

#include <iostream>
#include <type_traits>

enum class bitmap: unsigned char
{
    a = 0x01,
    b = 0x02,
    c = 0x04
};

bitmap operator& (bitmap x, bitmap y)
{
    typedef std::underlying_type<bitmap>::type uchar;
    return bitmap(uchar(x) & uchar(y));
}

bitmap operator| (bitmap x, bitmap y)
{
    typedef std::underlying_type<bitmap>::type uchar;
    return bitmap(uchar(x) | uchar(y));
}

bitmap operator^ (bitmap x, bitmap y)
{
    typedef std::underlying_type<bitmap>::type uchar;
    return bitmap(uchar(x) ^ uchar(y));
}

bool test(bitmap x)
{
    return std::underlying_type<bitmap>::type(x);
}

int main()
{
    bitmap v = bitmap::a | bitmap::b;
    if (test(v & bitmap::a)) {
        std::cout << "a ";
    }
    if (test(v & bitmap::b)) {
        std::cout << "b ";
    }
    if (test(v & bitmap::c)) {
        std::cout << "c ";
    }
    std::cout << '\n';
}

Ответ 2

Шаблоны хорошо работают с enum class, поэтому вы можете определить наборы операторов, которые работают с наборами аналогичных типов перечисления. Ключ состоит в том, чтобы использовать шаблон признаков, чтобы указать, какой интерфейс соответствует каждой подписке/подписке.

В начале:

enum class mood_flag {
    jumpy,
    happy,
    upset,
    count // size of enumeration
};

template<>
struct enum_traits< mood_flag > {
    static constexpr bool bit_index = true;
};

template< typename t >
struct flag_bits : std::bitset< static_cast< int >( t::count ) > {
    flag_bits( t bit ) // implicit
        { this->set( static_cast< int >( bit ) ); }

    // Should be explicit but I'm lazy to type:
    flag_bits( typename flag_bits::bitset set )
        : flag_bits::bitset( set ) {}
};

template< typename e >
typename std::enable_if< enum_traits< e >::bit_index,
    flag_bits< e > >::type
operator | ( flag_bits< e > set, e next )
    { return set | flag_bits< e >( next ); }

template< typename e >
typename std::enable_if< enum_traits< e >::bit_index,
    flag_bits< e > >::type
operator | ( e first, e next )
    { return flag_bits< e >( first ) | next; }

http://ideone.com/kJ271Z

GCC 4.9 сообщила, что некоторые неявные функции-члены были constexpr, в то время как я получал это для компиляции, поэтому шаблоны, вероятно, должны быть такими же.

Это, вероятно, также должно иметь свободную функцию to_scalar или что-то, что возвращает целочисленный тип без знака, заданный либо отдельным флагом, либо набором flag_bits.

Ответ 3

Как насчет определения FindFiles, так что он принимает std::initializer_list из FindFilesOptions.

void FindFiles(std::wstring const& pattern, std::initializer_list<FindFilesOptions> options)
{
  auto has_option = [&](FindFilesOptions const option)
  {
    return std::find(std::begin(options), std::end(options), option) != std::end(options);
  };

  if (has_option(FindFilesOptions::LocalSearch))
  {
    // ...
  }

  if (has_option(FindFilesOptions::RecursiveSearch))
  {
    // ...
  }

  if (has_option(FindFilesOptions::IncludeDotDirectories))
  {
    // ...
  }
}

Тогда вы можете называть его так:

FindFiles({}, {FindFilesOptions::RecursiveSearch, FindFilesOptions::IncludeDotDirectories});

Ответ 4

Проблема заключается не в явном типе перечисления, а в классе.

С С++ 11 перечисление как константа времени компиляции потеряет большой интерес по сравнению с связью constexpr, когда вам нужно оперировать значением (побитовая операция, инкремент и т.д.)

Ответ 5

Если вы не заботитесь о производительности, измените свои параметры на set<FindFilesOptions>!