枚举类型的模板化QDataStream操作符<<

3

在这个网站上看到了各种答案后,我尝试定义自己的模板函数,将任何枚举值写入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

但我无法让它正常工作。编译失败,显示以下消息:

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

为每个单独的枚举定义函数可以正常工作。我是否错误地使用了enable_if

3个回答

4
您需要将enable_if_t设置为模板参数的默认值。
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;
}

1
@DrumM 当T实际上是一个枚举类型时,std::enable_if_t<std::is_enum<T>::value>将评估为类型void。如果没有U=,这将被解析为尝试声明非类型模板参数(接受编译时值而不是类型的参数)的类型void - 但这是非法的,因为没有类型为void的值。U=意味着我们正在声明一个类型参数,并且该类型的默认参数是enable_if_t,它允许是void - aschepler
1
@DrumM 另一个有效的方法是 template <typename T, std::enable_if_t<std::is_enum<T>::value>* = nullptr>*使得非类型模板参数的类型为void*,而不是void,这是一种有效的类型。然后我们想要为该参数提供默认值,因为编译器没有自动推断的方法,所以我们只需添加= nullptr即可。 - aschepler
谢谢提供信息!这第二个模板参数(U)只是用来确保模板仅在其类型为“枚举”时执行?那我明白了,我想;-) - jaques-sam
1
@DrumM 对的 - 不过更准确地说,它确保模板在编译时不会产生可行的候选函数进行重载决议。执行更多是运行时的事情。这被称为SFINAE技术 - 替换失败不是一个错误。幸运的是,在C++20中我们将不需要这样的技巧:我们只需编写template <typename T> requires std::is_enum_v<T>,或者甚至只需编写template <Enum T>,如果Enum已被定义为概念。 - aschepler
是的,编译时,绝对没错;-) 感谢提供这么好的信息! - jaques-sam
显示剩余2条评论

2

我在尝试将enum Type : quint8 {...}流化到和从QSettings中读取时发现了这个问题,为了完整性,以下提供两种解决方法。


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;
}

对于QSettings,必须在Qt元对象系统中注册enum类型和流运算符。 使用Q_ENUM()(在QObjectQ_GADGET中)或Q_ENUM_NS()(在命名空间中),或在其他情况下使用QT_DECLARE_METATYPE()来注册枚举。 流运算符需要使用qRegisterMetaTypeStreamOperators<Type>("Type")单独注册。 还要注意,对于QFlags已经定义了流运算符,但是据我所知,它们仍然需要使用qRegisterMetaTypeStreamOperators进行注册以自动进行流式处理。

更新:
基于@DrumM的答案,MSVC17与C++14不喜欢上面的代码,以下是现在适用于我、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");

1

上面的解决方案来自aschepler是C++14。以下解决方案适用于C++11。

1. 基于aschepler的答案:

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;
}

请注意,这里不需要使用enable_if_tenable_if就足够了。

当使用C++11时,请勿使用此解决方案,正如aschepler在他的第一条评论中所提到的。

2.下一个解决方案基于返回类型

返回类型?是的,您可以期望在参数上使用enable_if,但它不适用于运算符重载:请参见cppreference

std::enable_if可用作附加函数参数(不适用于运算符重载),作为返回类型(不适用于构造函数和析构函数)或作为类模板或函数模板参数。

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;
}

请看这里,typename std::enable_if<...>::type 在这里是必要的,因为需要返回类型。

1
你在第一个答案中的运算符从未真正“禁用”特化。在http://coliru.stacked-crooked.com/a/c219f60d6b73e638中,模板运算符不幸比预期的`std::string`重载更匹配,尽管参数肯定不是枚举类型。`std::enable_if<false>是一个完整的、良好形式的类型,只是恰巧没有名为“type”的成员。你需要获得实际的失败才能使SFINAE工作,可以使用typename std::enable_if<...>::type(C++11或更高版本)或std::enable_if_t<...>::type`(C++14或更高版本)。 - aschepler
感谢@aschepler的精彩评论!很有道理! - jaques-sam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接