Добавление побитовых операций и преобразований в bool к перечисленным областям - исследование Christmastide - PullRequest
0 голосов
/ 17 декабря 2018

Допустим, я сумасшедший и решил создать следующее чудовище:

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

Цель состоит в том, чтобы:

  1. Иметь перечисление с областью видимости таким образом, чтобы значения были Foo::x и имеют тип Foo (симметрия!)
  2. Уметь выполнять некоторую "побитовую" арифметику с ними и получать Foo обратно
  3. Уметь проверять результат наноль
  4. Но не позволяйте людям обычно использовать перечисление как арифметический тип

Ради забавы я стараюсь избегать:

  1. Использованиестандартное перечисление
  2. Делать это вместо этого со свободными функциями IsFlagSet

На секунду игнорировать несоответствие невозможности выполнить проверку на ноль без предварительного & - или | -операция…

Кажется позором, что мои пользователи все еще могут «получить» EnumToBoolProxy (то есть proxyThing).Но, поскольку невозможно добавить каких-либо участников в Foo, и поскольку operator bool должен быть участником, я не могу найти какой-либо другой способ сделать это.

Конечно, это не такреальная проблема, так как они не могут многое сделать с EnumToBoolProxy.Но это все еще похоже на утечку абстракции, поэтому мне любопытно: правильно ли я говорю, что это просто невозможно по своей сути?Что нет способа «выбрать и выбрать» непрозрачность enoped-enum, как это?Или есть какой-то способ скрыть этот тип прокси, все еще используя его как средство преобразования в bool для проверки «результата» операций & / |?Как бы вы это сделали?

Ответы [ 2 ]

0 голосов
/ 18 декабря 2018

Итак, вот еще одно решение, возможно, более серьезное.

Оно соответствует всем вашим требованиям, даже "избегайте использования стандартного перечисления".За исключением того, что тип Foo-значения не Foo, а CRTP-Foo-ish.

User-API похож на реальное перечисление, но с некоторыми преимуществами перед моим другим ответом: - donне нужны жадные или защищенные 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);

}
0 голосов
/ 18 декабря 2018

Ну, это, вероятно, не то, что вы хотите, но вы сказали "скрыть этот тип прокси".Таким образом, вы могли бы скрыть это в следующем еще более чудовищном.Теперь результирующий тип - лямбда, скрывающая ваш прокси:)

#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.

}
...