【问题标题】:Creating a constexpr enum-like type创建类似 constexpr 枚举的类型
【发布时间】:2022-01-09 23:08:41
【问题描述】:

我一直使用enum class FooEnabled : bool { no, yes }; 作为创建类型安全bools 的一种方式。它工作得很好,除了我想添加对bool 的显式转换、operator! 等布尔运算符等。我可以这样做:

template <typename Tag>
class TypedBool {
    bool value;
    explicit constexpr TypedBool(bool b) noexcept : value(b) {}
public:
    static inline TypedBool no{false};
    static inline TypedBool yes{true};
    
    explicit constexpr operator bool() const noexcept { return value; }
    constexpr TypedBool operator!() const noexcept { return TypedBool{!value}; }
    // ...
};

using FooEnabled = TypedBool<struct FooEnabledTag>;

这很好用,但是 noyes 不是 constexpr,所以我不能做 if constexpr (FooEnabled::yes) { 的例子。如果我将noyes 改为static constexpr,clang 会感到不安,因为TypedBool 不是文字类型。这似乎是因为 TypedBool 在那个时候是不完整的。

最简单的例子是struct S { static constexpr S s; };,它给出了

error: constexpr variable cannot have non-literal type 'const S'
struct S { static constexpr S s; };
                              ^
note: incomplete type 'const S' is not a literal type
note: definition of 'S' is not complete until the closing '}'
struct S { static constexpr S s; };

有没有办法解决这个问题?我可以让noyes 成为隐式转换为TypedBool&lt;Tag&gt; 的不同类型,但这看起来很奇怪,因为那样auto x = FooEnabled::yes; 会使x 不是FooEnabled,所以

auto x = FooEnabled::yes;
[](FooEnabled& x) { x = !x; }(x);

会失败。

有没有办法让一个类包含属于它自己类型的static constexpr 成员?我开始想到的解决方案似乎都太丑陋了(而且这些解决方案也有constexpr 的限制)。

【问题讨论】:

  • 您始终可以创建yesno constexpr 函数。
  • 是的,但这不是类似枚举的 API。

标签: c++ constexpr type-safety


【解决方案1】:

我认为您可以在不更改范围枚举的情况下实现您的目标(支持 ! 运算符和显式转换为布尔值)。

所有作用域枚举似乎都支持显式转换为 bool,即使 bool 不是基础类型

enum class NotBool : int { No, Yes };
constexpr bool bad = NotBool::Yes; // compile error
constexpr bool yes = bool(NotBool::Yes);

您可以为所有范围内的枚举重载 ! 运算符,这些枚举具有带有模板和 std::enable_if 的基础布尔值:

template <typename T>
constexpr bool HasUnderlyingBool = std::is_same_v<std::underlying_type_t<T>, bool>;

template <typename T>
constexpr std::enable_if_t<HasUnderlyingBool<T>, T> operator !(const T& value) {
    return T(!bool(value));
}

enum class Bool : bool { No, Yes };
static_assert(!Bool::Yes == Bool::No);
static_assert(!Bool::No == Bool::Yes);

【讨论】:

    【解决方案2】:

    有没有办法让一个类包含属于它自己类型的静态 constexpr 成员?

    是的,有,只是将声明和定义分开,只有定义需要包含constexpr

    struct Foo {
        constexpr Foo(bool b): value(b){}
    
        static const Foo yes;
        static const Foo no;
    
        constexpr explicit operator bool() const noexcept{return value;}
        bool value;
    };
    // Mark inline if in a header.
    inline constexpr const Foo Foo::yes{true};
    inline constexpr const Foo Foo::no{false};
    
    int main(){
    
        if constexpr(Foo::yes){
            return 5;
        };
    }
    

    这不是声明与定义不同吗?

    所有三个编译器 g++,clang++,MSCV 19 都接受上面的代码。

    但是如果Foo 是一个模板,clang++ 就不再编译代码,就像在 cmets 中发现的那样。

    有一个question about this 暗示标准不禁止这样做。 不幸的是,C++17、C++20 标准也没有更明确,指出: 最终的 C++17 草案需要 [dcl.constexpr][Empahis mine]

    constexpr 说明符应仅应用于变量或变量模板的定义或函数或函数模板的声明。 consteval 说明符仅应用于函数或函数模板的声明。 使用 constexpr 或 consteval 说明符声明的函数或静态数据成员隐含地是内联函数或变量 ([dcl.inline])。 如果函数或函数模板的任何声明具有 constexpr 或 consteval 说明符,则其所有声明应包含相同的说明符。

    所以我认为这是允许的,但可能是由于疏忽而不是深思熟虑。我没有设法在标准中找到任何可以验证这种方法的示例。

    【讨论】:

    • 啊,谢谢,这让我也学到了一些东西:)
    • 您确定这在原始设置中有效吗? Foo 需要是一个模板。 It looks like clang has a problem exactly with that.
    • @n.1.8e9-where's-my-sharem。我对标签的需要感到困惑,并且正在解决问题的后半部分。 OP 可以指定任何要求。 G++ 编译您的示例。但我要添加一个部分,因为我认为这是一个略带灰色的区域。
    • 整个想法是拥有一堆不同的类型,它们的行为都像 bool,但不能从 bool 或彼此隐式转换。如果您只需要一种类型,只需使用 bool。
    • @n.1.8e9-where's-my-sharem。好的谢谢。在那种情况下,我不知道代码是否兼容并且 clang 是否存在错误。我相信标准并没有禁止这样做,但另一方面,如果将定义放在 .cpp 文件中,则它是不可实现的。
    【解决方案3】:

    这是我所知道的最接近的语法

    class TypedBool 
    {
    public:    
        explicit constexpr TypedBool(bool value) noexcept : 
            m_value{ value }
        {
        }
    
        static constexpr TypedBool no()
        {
            constexpr TypedBool value{ false };
            return value;
        }
     
        static constexpr TypedBool yes()
        {
            constexpr TypedBool value{ true };
            return value;
        }
    
        explicit constexpr operator bool() const noexcept { return m_value; }
    
    private:
        bool m_value;
    
    
    };
    
    int main()
    {
        constexpr TypedBool value{ true };
        static_assert(value);
        static_assert(TypedBool::yes());
        return 0;
    }
    

    【讨论】:

    • 是的。我真的很想让它看起来像enum class B : bool { no, yes };
    猜你喜欢
    • 2012-09-23
    • 1970-01-01
    • 2021-03-17
    • 2015-02-10
    • 1970-01-01
    • 1970-01-01
    • 2015-06-16
    • 2017-08-19
    相关资源
    最近更新 更多