dydil
dydil

Reputation: 1007

Templated QDataStream operator<< for enumerations

Following various answers on this site, I tried to define my own template function to write any enumeration value into a QDataStream.

template <typename T, typename std::enable_if_t<std::is_enum<T>::value>>
QDataStream &operator<<(QDataStream& stream, T enumValue)
{
    stream << static_cast<std::underlying_type_t<T>>(enumValue);
    return stream;
}

enum class MyEnum_e : int16_t{};

QDataStream stream;
MyEnum_e value;
stream << value; // Doesn't work

But I can't get to make it work. Compilation fails with following message:

no match for 'operator<<' (operand types are 'QDataStream' and 'MyEnum_e') stream << value;

Defining the function for each individual enumeration I have works as expected. Am I using enable_if wrong?

Upvotes: 3

Views: 1327

Answers (3)

Maxim Paperno
Maxim Paperno

Reputation: 4867

Since I found this while trying to stream an enum Type : quint8 {...} to & from QSettings, here's both ways for completeness.


template<class T, typename std::enable_if<std::is_enum<T>::value, T>::type* = nullptr>
QDataStream &operator<<(QDataStream& stream, const T &enumValue)
{
    return stream << static_cast<std::underlying_type_t<T>>(enumValue);
}

template<class T, typename std::enable_if<std::is_enum<T>::value, T>::type* = nullptr>
QDataStream &operator>>(QDataStream& stream, T &enumValue)
{
    std::underlying_type_t<T> val;
    stream >> val;
    enumValue = static_cast<T>(val);
    return stream;
}

Also for QSettings the enum type AND the stream operators must be registered in Qt meta object system. Enums are registered with the Q_ENUM() (in QObject or Q_GADGET) or Q_ENUM_NS() (in namespaces), or with QT_DECLARE_METATYPE() in other cases. The stream operators need to be registered separately with qRegisterMetaTypeStreamOperators<Type>("Type"). Also note that for QFlags there are already streaming operators defined, but AFAIK they still need to be registered with qRegisterMetaTypeStreamOperators to be streamed automatically.

UPDATE:
MSVC17 with C++14 didn't like my code above, so based on @DrumM's answer, here's what works for me now with that, MinGW 7.3, gcc 6.3 & 7.3:

template <typename T>
typename std::enable_if<std::is_enum<T>::value, QDataStream&>::type
operator<<(QDataStream& stream, const T &enumValue)
{
    return stream << static_cast<std::underlying_type_t<T>>(enumValue);
}

template <typename T>
typename std::enable_if<std::is_enum<T>::value, QDataStream&>::type
operator>>(QDataStream& stream, T &enumValue)
{
    std::underlying_type_t<T> val;
    stream >> val;
    enumValue = static_cast<T>(val);
    return stream;
}

// ... later
qRegisterMetaTypeStreamOperators<MyScope::MyEnum>("MyScope::MyEnum");

Upvotes: 2

jaques-sam
jaques-sam

Reputation: 2825

The solution from aschepler above is C++14. The following solutions work both for C++11.

1. Based on aschepler's answer:

template <typename T, typename U=std::enable_if<std::is_enum<T>::value>>
QDataStream &operator<<(QDataStream& stream, T enumValue)
{
    using underlying_type_t = typename std::underlying_type<T>::type;
    stream << static_cast<underlying_type_t>(enumValue);
    return stream;
}

See here that enable_if_t is not needed here, enable_if is sufficient.

Don't use this solution when using C++11 as mentioned by aschepler in his first comment.

2. The next solution is based using the return type

Return type?? Yes, you could expect to use enable_if on the the argument, but it's not applicable to operator overloads: See at cppreference:

std::enable_if can be used as an additional function argument (not applicable to operator overloads), as a return type (not applicable to constructors and destructors), or as a class template or function template parameter.

template <typename T>
typename std::enable_if<std::is_enum<T>::value, QDataStream&>::type
operator<<(QDataStream& stream, T enumValue)
{
    using underlying_type_t = typename std::underlying_type<T>::type;
    stream << static_cast<underlying_type_t>(enumValue);
    return stream;
}

See that typename std::enable_if<...>::type is necessary here because the type needs to be returned.

Upvotes: 1

aschepler
aschepler

Reputation: 72463

You need enable_if_t to be the default value for a template parameter.

template <typename T, typename U=std::enable_if_t<std::is_enum<T>::value>>
QDataStream &operator<<(QDataStream& stream, T enumValue)
{
    stream << static_cast<std::underlying_type_t<T>>(enumValue);
    return stream;
}

Upvotes: 4

Related Questions