【问题标题】:Refactoring scoped enum bitwise operator code duplication重构作用域枚举按位运算符代码重复
【发布时间】:2020-07-15 02:09:04
【问题描述】:

我有几个可以用作按位标志的作用域枚举。我已经为每种类型实现了相同的位运算符重载,如下所示:

ScopedEnumFoo& operator|=(ScopedEnumFoo& a, const ScopedEnumFoo& b) noexcept {
    using underlying = std::underlying_type_t<ScopedEnumFoo>;
    auto underlying_a = static_cast<underlying>(a);
    auto underlying_b = static_cast<underlying>(b);
    a = static_cast<ScopedEnumFoo>(underlying_a | underlying_b);
    return a;
}

ScopedEnumFoo operator|(ScopedEnumFoo a, const ScopedEnumFoo& b) noexcept {
    a |= b;
    return a;
}

ScopedEnumFoo& operator&=(ScopedEnumFoo& a, const ScopedEnumFoo& b) noexcept {
    using underlying = std::underlying_type_t<ScopedEnumFoo>;
    auto underlying_a = static_cast<underlying>(a);
    auto underlying_b = static_cast<underlying>(b);
    a = static_cast<ScopedEnumFoo>(underlying_a & underlying_b);
    return a;
}

ScopedEnumFoo operator&(ScopedEnumFoo a, const ScopedEnumFoo& b) noexcept {
    a &= b;
    return a;
}

除了作用域枚举的类型之外,对于需要用作标志性类型的每种类型,代码都是相同的。这会导致代码质量检查器对我重复了十几次(或更多)次的代码发出嘶嘶声。

我将如何对代码进行“重复数据删除”?有没有可能?

正如 Jason Turner recently 所说:

“我不会复制粘贴代码”是您编写好代码所能做的最重要的事情......

【问题讨论】:

    标签: c++ enums code-duplication scoped-enums


    【解决方案1】:

    当您在不同类型上运行相同的功能时,这是模板生成的,因此第一步是创建模板:

    template <class E>
    E& operator|=(E& a, const E& b) noexcept {
        using underlying = std::underlying_type_t<E>;
        auto underlying_a = static_cast<underlying>(a);
        auto underlying_b = static_cast<underlying>(b);
        a = static_cast<E>(underlying_a | underlying_b);
        return a;
    }
    

    这样做的问题是它很乐意接受任何类型,并且会造成麻烦并以通常意想不到的方式干扰代码的其他部分。我强烈建议不要使用此版本,即使运算符位于命名空间之后。

    因此,它需要限制为所需的类型。我将使用概念,因为它们更具表现力。对于 C++20 之前的版本,很容易转换为经典的 SFINAE 技术。

    一个快速的解决办法是只接受枚举:

    template <class E>
        requires std::is_enum_v<E>
    E& operator|=(E& a, const E& b) noexcept {
        using underlying = std::underlying_type_t<E>;
        auto underlying_a = static_cast<underlying>(a);
        auto underlying_b = static_cast<underlying>(b);
        a = static_cast<E>(underlying_a | underlying_b);
        return a;
    }
    

    这仍然是个问题,因为它会接受任何枚举类型,即使是你未定义的枚举类型。为了解决这个问题,有一些方法,比如为所有枚举添加一个哨兵/标签虚拟枚举值,但我选择的方法是为你的枚举设置一个特征(只需给它一个有意义的名称):

    template <class E> struct IsAwesomeEnum: std::false_type {};
    
    template <> struct IsAwesomeEnum<ScopedEnumFoo>  : std::true_type {};
    template <> struct IsAwesomeEnum<ScopedEnumBar>  : std::true_type {};
    
    template <class E>
        requires IsAwesomeEnum<E>::value
    E& operator|=(E& a, const E& b) noexcept {
        using underlying = std::underlying_type_t<E>;
        auto underlying_a = static_cast<underlying>(a);
        auto underlying_b = static_cast<underlying>(b);
        a = static_cast<E>(underlying_a | underlying_b);
        return a;
    }
    

    我会更进一步,为它定义一个概念:

    template <class E>
    concept AwesomeEnum = IsAwesomeEnum<E>::value;
    
    template <AwesomeEnum E>
    E& operator|=(E& a, const E& b) noexcept {
        using underlying = std::underlying_type_t<E>;
        auto underlying_a = static_cast<underlying>(a);
        auto underlying_b = static_cast<underlying>(b);
        a = static_cast<E>(underlying_a | underlying_b);
        return a;
    }
    

    为了完整起见,这里是一种非概念 SFINAE 方法:

    template <class E, class = std::enable_if_t<IsAwesomeEnum<E>::value>>
    E& operator|=(E& a, const E& b) noexcept {
        using underlying = std::underlying_type_t<E>;
        auto underlying_a = static_cast<underlying>(a);
        auto underlying_b = static_cast<underlying>(b);
        a = static_cast<E>(underlying_a | underlying_b);
        return a;
    }
    

    【讨论】:

    • 这是一个很好的答案(我等不及 MSVC 完全和原生地支持概念了)。我不得不使用 SFINAE 方法,但我能够将模板放在实用程序头中并使其成为任何声明自己为按位类型的范围枚举:template&lt;&gt; struct TypeUtils::is_bitflag_enum_type&lt;ScopedEnumFoo&gt; : std::true_type {}; 自动生成适当的函数。我还能够添加可增加和可减少的版本。
    • is_scoped_enum 将被添加到 C++23 中! :D
    • 是否有特定的原因(除了习惯方式)将右侧的操作数按const&amp; 而不是按值?由于枚举的基础类型是整数类型,不应该将它们按值作为首选方式吗?您对此有何看法?
    • @303 你是对的,按价值取值更有效。然而,这些运算符几乎肯定总是被内联的,在这种情况下它不会有任何区别。所以我并不太关心它。
    猜你喜欢
    • 2013-03-31
    • 1970-01-01
    • 1970-01-01
    • 2013-08-03
    • 2011-02-04
    • 1970-01-01
    • 1970-01-01
    • 2013-12-21
    • 1970-01-01
    相关资源
    最近更新 更多