重构作用域枚举按位运算符代码重复
Refactoring scoped enum bitwise operator code duplication
我有几个可以用作按位标志的作用域枚举。我已经为每种类型实现了相同的按位运算符重载,如下所示:
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;
}
除了作用域枚举的类型之外,代码对于需要用作标志类型的每种类型都是相同的。这会导致代码质量检查员发出嘶嘶声,说我已经重复了十几次(或更多次)代码。
我将如何对代码进行“去重”?有可能吗?
正如杰森·特纳 recently 所说:
'I will not copy-paste code' is the single most important thing you can do to write good code...
当您有相同的函数在不同类型上运行时,这就是模板生成的内容,因此第一步是创建模板:
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;
}
这仍然是有问题的,因为它会接受任何枚举类型,甚至是您未定义的枚举类型。要解决这个问题,有一些方法,比如为所有枚举添加一个 sentinel/tag 虚拟枚举值,但我选择的方法是为你的枚举添加一个特征(只需给它一个有意义的名称):
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;
}
我有几个可以用作按位标志的作用域枚举。我已经为每种类型实现了相同的按位运算符重载,如下所示:
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;
}
除了作用域枚举的类型之外,代码对于需要用作标志类型的每种类型都是相同的。这会导致代码质量检查员发出嘶嘶声,说我已经重复了十几次(或更多次)代码。
我将如何对代码进行“去重”?有可能吗?
正如杰森·特纳 recently 所说:
'I will not copy-paste code' is the single most important thing you can do to write good code...
当您有相同的函数在不同类型上运行时,这就是模板生成的内容,因此第一步是创建模板:
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;
}
这仍然是有问题的,因为它会接受任何枚举类型,甚至是您未定义的枚举类型。要解决这个问题,有一些方法,比如为所有枚举添加一个 sentinel/tag 虚拟枚举值,但我选择的方法是为你的枚举添加一个特征(只需给它一个有意义的名称):
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;
}