【问题标题】:Templated QDataStream operator<< for enumerations用于枚举的模板化 QDataStream 运算符<<
【发布时间】:2017-03-18 00:10:35
【问题描述】:

根据本网站上的各种答案,我尝试定义自己的模板函数以将任何枚举值写入 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

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

'operator

为我所拥有的每个单独枚举定义函数按预期工作。我是不是用错了enable_if

【问题讨论】:

    标签: c++ qt


    【解决方案1】:

    您需要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;
    }
    

    【讨论】:

    • 只是一个问题,为什么需要U=?这很奇怪,因为它没有在函数体内使用。没有,它会失败并出现错误illegal type for non-type template parameter
    • @DrumM 当T 实际上是一个枚举类型时,std::enable_if_t&lt;std::is_enum&lt;T&gt;::value&gt; 的计算结果为void 类型。如果没有U=,这将被解析为尝试声明void 类型的非类型模板参数(接受编译时值而不是类型) - 但这是非法的,因为没有类型的值voidU= 表示我们改为声明一个类型参数,该类型的默认参数是 enable_if_t 事物,允许为 void
    • @DrumM 另一种有效的方法是template &lt;typename T, std::enable_if_t&lt;std::is_enum&lt;T&gt;::value&gt;* = nullptr&gt;* 使非类型模板参数的类型为void* 而不是void,这是一个有效类型。然后我们想为该参数提供一个默认值,因为编译器无法自动推断它,所以我们只需添加= nullptr
    • 感谢您的信息!第二个模板参数 (U) 仅用于确保模板仅在其类型为 enum 时才执行?比我想的要多;-)
    • @DrumM 是的——尽管从技术上讲,它确保模板在编译时不会为重载解析生成可行的候选函数。执行更多的是运行时的事情。这称为 SFINAE 技术 - 替换失败不是错误。幸运的是,在 C++20 中我们不需要这样的技巧:我们将简单地编写 template &lt;typename T&gt; requires std::is_enum_v&lt;T&gt;,或者如果 Enum 已被定义为一个概念,甚至只是 template &lt;Enum T&gt;
    【解决方案2】:

    解决方案from aschepler above 是 C++14。以下解决方案适用于 C++11。

    1。根据阿舍普勒的回答:

    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_t这里不需要,enable_if就足够了。

    如 aschepler 在他的第一条评论中提到的,在使用 C++11 时不要使用此解决方案。

    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&lt;...&gt;::type,因为需要返回类型。

    【讨论】:

    • 您在回答中的操作员从未真正“禁用”专业化。在 coliru.stacked-crooked.com/a/c219f60d6b73e638 中,不幸的是,模板运算符比预期的 std::string 重载匹配得更好,尽管事实上参数肯定不是枚举。 std::enable_if&lt;false&gt; 是一个结构良好且完整的类型,只是碰巧没有名为 type 的成员。您需要通过使用typename std::enable_if&lt;...&gt;::type(C++11 或更高版本)或std::enable_if_t&lt;...&gt;::type(C++14 或更高版本)来让 SFINAE 正常工作。
    • 感谢@aschepler 的精彩评论!有道理!
    【解决方案3】:

    由于我在尝试将enum Type : quint8 {...} 流式传输到QSettings 和从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;
    }
    

    对于QSettingsenum 类型也必须在 Qt 元对象系统中注册流操作符。枚举使用Q_ENUM()(在QObjectQ_GADGET)或Q_ENUM_NS()(在命名空间中)注册,或者在其他情况下使用QT_DECLARE_METATYPE() 注册。流操作符需要单独注册qRegisterMetaTypeStreamOperators&lt;Type&gt;("Type")。另请注意,对于 QFlags,已经定义了流式操作符,但 AFAIK 仍然需要向 qRegisterMetaTypeStreamOperators 注册才能自动进行流式传输。

    更新:
    带有 C++14 的 MSVC17 不喜欢我上面的代码,所以根据@DrumM 的回答,这就是现在对我有用的方法,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");
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-03-02
      • 1970-01-01
      • 2011-01-28
      • 1970-01-01
      • 2010-12-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多