13

Let's say I have a set of flags and a class like this:

/// <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 */
}

and I want a caller to be able to select more than one option:

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

Is it possible to support this in a strongly-typed way with C++11 enum class, or do I have to revert to untyped enumerations?

(I know the caller could static_cast to the underlying type and static_cast back, but I don't want the caller to have to do that)

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • There's also [`std::bitset`](http://en.cppreference.com/w/cpp/utility/bitset) to pass flags. – dyp Sep 01 '13 at 00:26

5 Answers5

14

It is certainly possible to use enum classes for bitmaps. It is, unfortunately, a bit painful to do so: You need to define the necessary bit operations on your type. Below is an example how this could look like. It would be nice if the enum classes could derive from some other type which could live in a suitable namespace defining the necessary operator boilerplate code.

#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';
}
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Actually, I don't think that is painful at all -- I only want to allow callers to OR them together so all I'd need would be operator|. +1 – Billy ONeal Aug 31 '13 at 23:57
  • @BillyONeal: I'm opposed to any sort of code duplication. I would prefer if the operators could be conjured up using something like `enum class bitmap: bitmaps::base { ... };` with the above operators defined as templates in the namespace `bitmaps`. – Dietmar Kühl Sep 01 '13 at 00:00
  • you could always stamp out the operators with macros. – Billy ONeal Sep 01 '13 at 00:03
  • 1
    Why not use `std::underlying_type::type` instead of repeating it in the typedef? – dyp Sep 01 '13 at 00:17
  • @DyP: good point: probably because I haven't used `enum class`es much, yet, and I wasn't aware of this type traits! I have fixed the answer. – Dietmar Kühl Sep 01 '13 at 00:19
  • 2
    BTW, the correct term is "bitfield", not "bitmap". A bitmap, particularly in image circles, is something quite different. – Nicol Bolas Sep 01 '13 at 00:36
  • The `uchar` identifier can mean something other than `unsigned char` if the enum changes (far away from the actual operators, so out of sight). Why not just call it `type`? – DanielKO Sep 01 '13 at 00:50
  • "I would prefer if the operators could be conjured up by using something like `enum class bitmap: bitmaps::base`" Could use [a class with an anonymous enum member](http://coliru.stacked-crooked.com/A/31f81324fe6f97ae), with the downside that the *enumerators* themselves are still implicitly convertible to an integral type :/ – dyp Sep 01 '13 at 00:54
  • 1
    @DyP: Maybe I should settle with defining the `enum` in a suitable location and using a `typedef` to make it accessible where it is actually needed. [Here](http://ideone.com/FKiWX7) is a complete example. – Dietmar Kühl Sep 01 '13 at 01:31
  • Given that you have control over the semantics of `operator|`, you can allow it to work on bit indexes and forgo defining the enumerators as powers of two. – Potatoswatter Sep 01 '13 at 02:40
  • @DietmarKühl Interesting! (Any reason to use `noexcept(true)` over just `noexcept`?) – dyp Sep 01 '13 at 11:46
  • @DyP: The only reason to use `noexcept(true)` is to make more explicit. There is no technical reason. – Dietmar Kühl Sep 01 '13 at 12:00
  • IMO this is broken. `a | b` is not a valid member of `bitmap` yet through casting you are claiming it is. – Shea Levy Feb 22 '14 at 14:47
  • @SheaLevy `operator|` is not a member of any enumeration. There's a built-in operator `|` (built-in = not a function) that operates on unscoped enumerations. You can also define (some) operators as non-member (= free) functions. In `bitmap(uchar(x) | uchar(y));`, the `|` is *not* operating on an enumeration type, but on an integral type, such as `int`. – dyp Feb 22 '14 at 16:35
  • @dyp that's not my point. With the way you defined `operator |`, I can create a `bitmap` whose underlying value is `Ox03`, but `Ox03` is *not* a legal `bitmap` value! – Shea Levy Feb 22 '14 at 16:46
  • 3
    @SheaLevy Well there's no enumerator with the value `0x03`, but `0x03` is a "valid" value for a variable of type `bitmap`, see http://stackoverflow.com/q/18195312/420683 – dyp Feb 22 '14 at 17:14
  • "It would be nice if the enum classes could derive from some other type which could live in a suitable namespace defining the necessary operator boilerplate code." — you can leverage ADL, templating and the traits idiom to avoid boilerplate. See http://stackoverflow.com/a/12927952/153285 . (Sorry for dead-posting!) – Potatoswatter Mar 01 '14 at 00:59
  • @DietmarKühl I tripped up on C-style cast you use `uchar(…)` mistaking it for the underlying type's constructor and that an implicit conversion is happening from enum to the underlying type - which isn't the case. Nice answer btw! – legends2k Mar 18 '15 at 15:13
4

Templates play well with enum class so you can define sets of operators that work on sets of similar enumeration types. The key is to use a traits template to specify what interface(s) each enumeration conforms/subscribes to.

As a start:

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 reported that some implicit member functions were constexpr while I was getting this to compile, so the templates should probably be so as well.

This should probably also have a free function to_scalar or something which returns an unsigned integer type given either an individual flag or a flag_bits set.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
1

How about defining FindFiles so that it takes std::initializer_list of 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))
  {
    // ...
  }
}

Then you could call it like so:

FindFiles({}, {FindFilesOptions::RecursiveSearch, FindFilesOptions::IncludeDotDirectories});
Natok
  • 131
  • 1
  • 4
  • Interesting. Now if the compiler I was using only supported `initializer_list`. +1 – Billy ONeal Sep 01 '13 at 00:50
  • Then, just use std::vector. – Natok Sep 01 '13 at 00:53
  • That makes the caller unnecessarily complex. – Billy ONeal Sep 01 '13 at 00:53
  • @BillyONeal Instead of an `initializer_list`, maybe a function template with a parameter pack? – dyp Sep 01 '13 at 00:57
  • The function call (or constructor in your case) would be exactly the same. All you would need to change is "std::initializer_list options" to "std::vector options". – Natok Sep 01 '13 at 00:59
  • 2
    @Natok The ctor of `vector` that allows this... is taking an `initializer_list`. Same problem probably. – dyp Sep 01 '13 at 01:01
  • @DyP, you are correct, my bad. Well, the problem with variadic template parameters is that the types are not enforced to be of the same type; not sure if it's a problem though. – Natok Sep 01 '13 at 01:08
0

The problem is not the explicit enum type but the class scope.

With C++11, enum as compile time constant loose a lot of interest compared to a bunch of constexpr when you need operate on the value ( bitwise operation, incrementation, etc)

galop1n
  • 8,573
  • 22
  • 36
  • 1
    Making the values `constexpr`s would allow someone to pass in any old integral type to `FindFiles`, which I do not want to allow. – Billy ONeal Aug 31 '13 at 23:53
  • I was a bit fast, the idea is to write a small template wrapper over a scalar type, taking a second class type as a tag ( like iterator_category ) to prevent mix of two sets of constants. – galop1n Sep 01 '13 at 00:10
  • That would require me to make `FindFiles::FindFiles(std::wstring const&, options)` into a template. – Billy ONeal Sep 01 '13 at 00:49
0

If you don't care about performance, change your options to set<FindFilesOptions>!

Neil Kirk
  • 21,327
  • 9
  • 53
  • 91