Home > Back-end >  Creating a constexpr enum-like type
Creating a constexpr enum-like type

Time:12-04

I've been using enum class FooEnabled : bool { no, yes }; as a way to create type-safe bools. It works well, except I'd like to add explicit conversion to bool, Boolean operators like operator!, etc. I can do that like this:

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

That works great, however no and yes aren't constexpr, so I can't do if constexpr (FooEnabled::yes) { for example. If I make no and yes be instead static constexpr, clang is upset because TypedBool is not a literal type. That appears to be because TypedBool is incomplete at that point.

The simplest example of this is struct S { static constexpr S s; }; which gives

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

Is there any way around this? I could make no and yes be a different type that implicitly converts to TypedBool<Tag>, but that seems weird, because then auto x = FooEnabled::yes; would make x not be a FooEnabled, so

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

would fail.

Is there any way to have a class contain static constexpr members that are its own type? The solutions I'm starting to think of all seem too ugly to mention (and those also have constexpr limitations).

CodePudding user response:

Is there any way to have a class contain static constexpr members that are its own type?

Yes, there is, just split the declaration from the definition, only the definition needs to contain 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;
    };
}

CodePudding user response:

This is the closest syntax I know works

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;
}
  • Related