2

How should the Q_FLAG_NS macro be used?

My reading of Q_FLAG_NS and KDAB's New Qt Support Namespaces suggests to me that using this macro should provide bitwise operators, but no matter what I try,

../main.cpp:11:11: error: invalid operands to binary expression 
('App::ComponentRequirementState' and 'App::ComponentRequirementState')
    r = r | App::ComponentRequirementState::AlwaysRequired;
        ~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

I placed a minimum code example here.

The weird thing is, I can use bitwise operators inside the definition of the ComponentRequirementStat enum, just not outside of it.

Am I using this macro wrong? Or does it simply not work?

When I manually define the operators, say,

auto operator|(const App::ComponentRequirementState a, App::ComponentRequirementState b) -> App::ComponentRequirementState
{
    return static_cast<App::ComponentRequirementState>(static_cast<unsigned int>(a) | static_cast<unsigned int>(b));
}

Then it works.

ismail
  • 46,010
  • 9
  • 86
  • 95
Matt
  • 1,928
  • 24
  • 44

2 Answers2

2

Using Qt

Q_FLAGS_NS does not provide bitwise operation support: it merely registers the type in the Qt metaobject system.

If you would like to register the type and provide type-safe, bitwise operator support, you should use QFlags.

An example from the documentation is:

class MyClass
{
public:
    enum Option {
        NoOptions = 0x0,
        ShowTabs = 0x1,
        ShowAll = 0x2,
        SqueezeBlank = 0x4
    };
    Q_DECLARE_FLAGS(Options, Option)
    ...
};

Q_DECLARE_OPERATORS_FOR_FLAGS(MyClass::Options)

You may also just skip most of this and do:

// not type-safe, unscoped enum. You should likely define this
// in a namespace or class.  
enum Option {
    NoOptions = 0x0,
    ShowTabs = 0x1,
    ShowAll = 0x2,
    SqueezeBlank = 0x4
};

// type-safe flag
using Options = QFlags<Option>;

You may also use unscoped enums for this exact purpose, or use a macro to help you provide bitwise operator support for a scoped enum:

Type-safe Bitwise Operations with Scoped Enums

Here is a macro you may freely use (public domain, my own code, although you should likely modify the macro to ensure it has a prefix to avoid any name collisions) to add bitwise operations to a scoped enum. This currently requires C++14 support, however, changing std::underlying_type_t<T> to typename std::underlying_type<T>::type allows the macro to work in C++11.

Use

enum class enum1_t
{
    A = 1,
    B,
    C,
    D,
    E,
};


enum class enum2_t
{
    F = 1,
    G,
    H,
    I,
    J,
};

ENUM_FLAG(enum1_t)            // allow bitwise operations for enum1_t and enum1_t
ENUM_FLAG(enum1_t, enum2_t)   // allow bitwise operations for enum1_t and enum2_t

Code

#include <type_traits>
#include <cstdint>

// HELPERS
// -------

/**
 *  \brief Get enum underlying type.
 */
template <typename T>
inline std::underlying_type_t<T> int_t(T t)
{
    return static_cast<std::underlying_type_t<T>>(t);
}

// MACROS
// ------

/**
 *  \brief Macro to define enum operators between enumerations.
 *
 *  Supports `&`, `&=`, `|`, `|=`, `^`, `^=`, `~`, and bool conversion.
 */
#define ENUM_FLAG2(lhs_t, ths_t)                                        \
    /*  \brief Bitwise or operator. */                                  \
    inline lhs_t operator|(lhs_t lhs, ths_t rhs) noexcept               \
    {                                                                   \
        return static_cast<lhs_t>(int_t(lhs) | int_t(rhs));             \
    }                                                                   \
                                                                        \
    /*  \brief Bitwise or assignment operator. */                       \
    inline lhs_t & operator|=(lhs_t &lhs, ths_t rhs) noexcept           \
    {                                                                   \
        lhs = static_cast<lhs_t>(int_t(lhs) | int_t(rhs));              \
        return lhs;                                                     \
    }                                                                   \
                                                                        \
    /*  \brief Bitwise and operator. */                                 \
    inline lhs_t operator&(lhs_t lhs, ths_t rhs) noexcept               \
    {                                                                   \
        return static_cast<lhs_t>(int_t(lhs) & int_t(rhs));             \
    }                                                                   \
                                                                        \
    /*  \brief Bitwise and assignment operator. */                      \
    inline lhs_t & operator&=(lhs_t &lhs, ths_t rhs) noexcept           \
    {                                                                   \
        lhs = static_cast<lhs_t>(int_t(lhs) & int_t(rhs));              \
        return lhs;                                                     \
    }                                                                   \
                                                                        \
    /*  \brief Bitwise xor operator. */                                 \
    inline lhs_t operator^(lhs_t lhs, ths_t rhs) noexcept               \
    {                                                                   \
        return static_cast<lhs_t>(int_t(lhs) ^ int_t(rhs));             \
    }                                                                   \
                                                                        \
    /*  \brief Bitwise xor assignment operator. */                      \
    inline lhs_t & operator^=(lhs_t &lhs, ths_t rhs) noexcept           \
    {                                                                   \
        lhs = static_cast<lhs_t>(int_t(lhs) ^ int_t(rhs));              \
        return lhs;                                                     \
    }


/**
 *  \brief Set enumeration flags within the same enum.
 */
#define ENUM_FLAG1(enum_t)                                              \
    ENUM_FLAG2(enum_t, enum_t)                                          \
                                                                        \
    /*  \brief Bitwise negation operator. */                            \
    inline enum_t operator~(enum_t value) noexcept                      \
    {                                                                   \
        return static_cast<enum_t>(~int_t(value));                      \
    }                                                                   \
                                                                        \
    /*  \brief Negation operator. */                                    \
    inline bool operator!(enum_t value) noexcept                        \
    {                                                                   \
        return int_t(value) == 0;                                       \
    }

/**
 *  \brief Macros to grab the proper bit-wise flag setter.
 *  `ENUM_ID` is required for MSVC compatibility, since MSVC
 *  has issues in expanding `__VA_ARGS__` for the dispatcher.
 *  Don't remove it, even if the above code works without it 
 *  for GCC and Clang.
 */
#define ENUM_ID(x) x
#define GET_ENUM_FLAG(_1,_2,NAME,...) NAME
#define ENUM_FLAG(...) ENUM_ID(GET_ENUM_FLAG(__VA_ARGS__, ENUM_FLAG2, ENUM_FLAG1)(__VA_ARGS__))
Alex Huszagh
  • 13,272
  • 3
  • 39
  • 67
  • Thanks! I'll try this asap, one question though, you, and the docs, both do `Q_DECLARE_FLAGS(Options, Option)`, why the singular and plural for `Option`? – Matt Jan 15 '18 at 21:45
  • @Matt The plural form creates a type-safe flag from the class-scoped enum (of type `QFlags`). The `Q_DECLARE_OPERATORS_FOR_FLAGS` then exports all the operators to the global scope. You may omit a fair bit of this, but I've mainly resorted to using my own macro and then registering it with the Qt object system in my own code. Both work, I just like C++11 scoped enums more than Qt-specific scoped enums. – Alex Huszagh Jan 15 '18 at 21:48
  • @Matt I updated my example regardless. Basically, the class is mostly to prevent the unscoped enum values from escaping to the global namespace, and the macros are just helpers to instantiate a template. – Alex Huszagh Jan 15 '18 at 21:51
1

Q_FLAG_NS is not the problem. This is caused by the new C++11 scoped enumerations (enum class). They do not implicitly convert to integral type and you cannot use them for bit-wise operations which are defined only for integral types [1][2].

You have to provide these operations yourself or use the unscoped, old, enumeration.

One issue by providing these operations is that it would require to define all possible combinations of flags as enum values or return integral type:

??? operator|(App::ComponentRequirementState lhs, App::ComponentRequirementState rhs);
Mihayl
  • 3,821
  • 2
  • 13
  • 32
  • Yep, I actually wrote all of the combinations out. That's where I was hoping `Q_FLAG_NS` would help. And because I couldn't think of a templated solution, I've actually done it twice. I'm attempting to implement Alexander Huszagh templated solution now. – Matt Jan 15 '18 at 21:55
  • I tried that (in the namespace), it still didn't provide operators for me. I didn't attempt that in the minimal example, maybe I could try there to see if I can get it working. – Matt Jan 15 '18 at 21:58
  • 1
    `??? operator|(App::ComponentRequirementState lhs, App::ComponentRequirementState rhs);` I actually had to do the latter extensively in my own code, so I wrote a macro to do it for me. Macros may be evil, but in this case (type-safety), I find I can relax the rule. I really would like other people to take my work, since it means others don't have to write the same boilerplate I did. – Alex Huszagh Jan 15 '18 at 22:01
  • 1
    Classic enums alwo won't work with auto, for similar reasons, but https://godbolt.org/g/pB7qFv – Mihayl Jan 15 '18 at 22:03
  • That doesn't enforce type safety, sadly. Otherwise, it would be great. I can still do `Fruit::ORANGE | Vegetable::TOMATO` and not have any issues, even if `Fruit` and `Vegetable` are mutually exclusive. – Alex Huszagh Jan 15 '18 at 22:12