Добавление побитовых операций и преобразований в bool к перечисленным областям - исследование Christmastide
Допустим, я сумасшедший и решил создать следующее чудовище:
#include <type_traits>
#include <iostream>
// Utility proxy type - convertible back to E but also permits bool conversion
// for use in conditions.
//
// e.g.
// Foo f = Foo::Bar & Foo::Baz;
// if (f & Foo::Baz) { /* ... */ }
//
template <typename E, typename = std::enable_if_t<std::is_enum_v<E>, void>>
struct EnumToBoolProxy
{
operator E() const
{
return _val;
}
explicit operator bool()
{
using UT = std::underlying_type_t<E>;
return static_cast<UT>(_val) != 0;
}
private:
const E _val;
EnumToBoolProxy(const E val) : _val(val) {}
friend EnumToBoolProxy operator&(const E, const E);
friend EnumToBoolProxy operator|(const E, const E);
};
enum class Foo
{
Bar = 1, Baz = 2, Boi = 4
};
EnumToBoolProxy<Foo> operator&(const Foo lhs, const Foo rhs)
{
using UT = std::underlying_type_t<Foo>;
return static_cast<Foo>(static_cast<UT>(lhs) & static_cast<UT>(rhs));
}
EnumToBoolProxy<Foo> operator|(const Foo lhs, const Foo rhs)
{
using UT = std::underlying_type_t<Foo>;
return static_cast<Foo>(static_cast<UT>(lhs) | static_cast<UT>(rhs));
}
int main()
{
// Good
if ((Foo::Bar | Foo::Baz) & Foo::Baz)
std::cout << "Yay\n";
// Fine
const bool isFlagSet((Foo::Bar | Foo::Baz) & Foo::Baz);
std::cout << isFlagSet << '\n';
// Meh
auto proxyThing = (Foo::Bar | Foo::Baz) & Foo::Baz;
}
Цель состоит в том, чтобы:
- Имейте перечисление в области видимости так, чтобы значения были
Foo::x
и имеют типFoo
(Симметрия!) - Быть в состоянии сделать некоторую "побитовую" арифметику на них и получить
Foo
назад - Уметь проверить результат на ноль
- Но не позволяйте людям вообще использовать перечисление как арифметический тип
Ради интереса я стараюсь избегать:
- Использование стандартного перечисления
- Делаем это вместо этого со свободными функциями
IsFlagSet
Игнорирование на секунду несоответствия невозможности выполнить проверку на нулевое значение без предварительного &
- или же |
-operation...
Кажется позором, что мои пользователи все еще могут "получить" EnumToBoolProxy
(т.е. proxyThing
). Но, поскольку невозможно добавить каких-либо участников в Foo
, и с тех пор operator bool
Должно быть, я не могу найти другой способ пойти по этому поводу.
Конечно, это не настоящая проблема, так как они мало что могут сделать с EnumToBoolProxy
, Но это все еще похоже на утечку абстракции, поэтому мне любопытно: правильно ли я говорю, что это просто невозможно по своей сути? Что нет способа "выбрать и выбрать" непрозрачность enoped-enum, как это? Или есть какой-то способ скрыть этот тип прокси, все еще используя его как средство преобразования в bool, чтобы проверить "результат" &
/|
операции? Как бы вы это сделали?
2 ответа
Итак, вот другое решение, возможно, более серьезное.
Он соответствует всем вашим требованиям, даже "избегайте использования стандартного перечисления". За исключением того, что тип Foo-значения не Foo, а CRTP-Foo-ish.
User-API похож на настоящий enum, но с некоторыми преимуществами перед моим другим ответом:
- не нужны жадные операторы или операторы, защищенные SFINAE. - Нет прокси класса больше. - Это constexpr
, - Нулевую проверку можно сделать напрямую, без необходимости звонить &
или же |
до.
#include <type_traits>
#include <iostream>
// Utility proxy type - convertible back to E but also permits bool conversion
// for use in conditions.
//
// e.g.
// Foo f = Foo::Bar & Foo::Baz;
// if (f & Foo::Baz) { /* ... */ }
//
template<unsigned x, typename Base>
struct EnumVal : std::integral_constant<unsigned, x> {
};
struct Foo;
template<unsigned x>
using FooVal = EnumVal<x, Foo>;
struct Foo {
static constexpr FooVal<1> Bar;
static constexpr FooVal<2> Baz;
static constexpr FooVal<4> Boi;
};
template<unsigned lhs, unsigned rhs>
EnumVal<(lhs & rhs), Foo> constexpr operator&( EnumVal<lhs, Foo> , EnumVal<rhs, Foo> ) {
return {};
}
template<unsigned lhs, unsigned rhs>
EnumVal<(lhs | rhs), Foo> constexpr operator|( EnumVal<lhs, Foo> , EnumVal<rhs, Foo> ) {
return {};
}
template<typename T>
constexpr void print_type(T) {
static_assert(std::is_same_v<T, void>, "YOU WANTED TO READ THIS TYPE!");
}
int main() {
// Not an arithmetic type :)
static_assert(!std::is_arithmetic_v<decltype(Foo::Bar)>);
static_assert(Foo::Bar);
static_assert(!(Foo::Bar & Foo::Baz));
// Good
if ((Foo::Bar | Foo::Baz) & Foo::Baz)
std::cout << "Yay\n";
// Fine
const bool isFlagSet = (Foo::Bar | Foo::Baz) & Foo::Baz;
std::cout << isFlagSet << '\n';
// Finally really not a proxy thing anymore!
auto proxyThing = (Foo::Bar | Foo::Baz) & Foo::Baz;
// print_type(proxyThing);
}
Ну, это, вероятно, не то, что вы хотите, но вы сказали "скрыть этот тип прокси". Таким образом, вы могли бы скрыть это в следующем еще более чудовищном. Теперь результирующий тип - лямбда, скрывающая ваш прокси:)
#include <type_traits>
#include <iostream>
// Utility proxy type - convertible back to E but also permits bool conversion
// for use in conditions.
//
// e.g.
// Foo f = Foo::Bar & Foo::Baz;
// if (f & Foo::Baz) { /* ... */ }
//
auto lam = [](auto e) {
struct Key {};
//template <typename E, typename = std::enable_if_t<std::is_enum_v<E>, void>>
struct EnumToBoolProxy {
using E = decltype(e);
operator E() const {
return _val;
}
explicit operator bool() {
using UT = std::underlying_type_t<E>;
return static_cast<UT>(_val) != 0;
}
EnumToBoolProxy(const E val, Key) : _val(val) {}
private:
const E _val;
};
return EnumToBoolProxy(e, Key{});
};
enum class Foo {
Bar = 1, Baz = 2, Boi = 4
};
auto operator&(const Foo lhs, const Foo rhs) {
using UT = std::underlying_type_t<Foo>;
return lam(static_cast<Foo>(static_cast<UT>(lhs) & static_cast<UT>(rhs)));
}
template<typename T, std::enable_if_t<std::is_same_v<T, decltype(lam)>>>
auto operator&(T lhs, const Foo rhs) {
using UT = std::underlying_type_t<Foo>;
return lam(static_cast<Foo>(static_cast<UT>(lhs) & static_cast<UT>(rhs)));
}
auto operator|(const Foo lhs, const Foo rhs) {
using UT = std::underlying_type_t<Foo>;
return lam(static_cast<Foo>(static_cast<UT>(lhs) | static_cast<UT>(rhs)));
}
int main() {
lam(Foo::Bar);
// Good
if ((Foo::Bar | Foo::Baz) & Foo::Baz)
std::cout << "Yay\n";
// Fine
const bool isFlagSet((Foo::Bar | Foo::Baz) & Foo::Baz);
std::cout << isFlagSet << '\n';
// OK, still a proxy thing
auto proxyThing = (Foo::Bar | Foo::Baz) & Foo::Baz;
using Proxy = decltype(proxyThing);
//Proxy proxy2(Foo::Bar); // Does not work anymore.
}