首页 > 解决方案 > 向作用域枚举添加按位运算和转换为布尔值 - 圣诞探索

问题描述

假设我疯了,并决定创建以下怪物:

#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我的用户仍然可以“获得” a (ie proxyThing) ,这似乎是一种耻辱。但是,由于不可能向 中添加任何成员Foo,并且operator bool必须是成员,所以我似乎找不到任何其他方法来解决这个问题。

当然这不是一个真正的问题,因为他们不能对EnumToBoolProxy. 但它仍然感觉像是一个抽象泄漏,所以我很好奇:我是否正确地说这本质上是不可能的?没有办法像这样“挑选”范围枚举的不透明度?或者是否有某种方法可以隐藏此代理类型,同时仍将其用作转换为布尔的工具来检查&/|操作的“结果”?你会怎么做?

标签: c++c++17enum-class

解决方案


好吧,这可能不是您想要的,但是您说“隐藏此代理类型”。因此,您可以将其隐藏在下面的更多怪物中。现在生成的类型是隐藏您的代理的 lambda :)

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

}

推荐阅读