0

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

Ben
  • 9,184
  • 1
  • 43
  • 56

3 Answers3

1

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

Isn't this different declaration vs definition ?

All three compilers g++,clang++,MSCV 19 accept the code above.

But if Foo is a template, clang++ doesn't compile the code anymore, as discovered in comments.

There is a question about this hinting the standard does not forbid this. Unfortunately, C++17, C++20 standards are no more explicit either, stating: The final C++17 draft requires of [dcl.constexpr][Empahis mine]

The constexpr specifier shall be applied only to the definition of a variable or variable template or the declaration of a function or function template. The consteval specifier shall be applied only to the declaration of a function or function template. A function or static data member declared with the constexpr or consteval specifier is implicitly an inline function or variable ([dcl.inline]). If any declaration of a function or function template has a constexpr or consteval specifier, then all its declarations shall contain the same specifier.

So my take from this is this is allowed but maybe due to omission rather than deliberation. I did not manage to find any examples in the Standard that would validate this approach.

Quimby
  • 17,735
  • 4
  • 35
  • 55
  • Ah thanks, that made me learn something too :) – Pepijn Kramer Dec 03 '21 at 16:00
  • 3
    Are you sure this works in the original setting? `Foo` needs to be a template. [It looks like clang has a problem exactly with that](https://godbolt.org/z/hxr73x453). – n. m. could be an AI Dec 03 '21 at 16:12
  • @n.1.8e9-where's-my-sharem. I am confused about the need for tag at all and was addressing the second half of the question. OP can specify any requirements. G++ compiles your example. But I am adding a section because I believe this is slightly grey area. – Quimby Dec 03 '21 at 16:19
  • The whole idea is to have a bunch of different types that all behave like bool but are not implicitly convertible from bool, or from each other. If you need only one type, just use bool. – n. m. could be an AI Dec 03 '21 at 16:26
  • @n.1.8e9-where's-my-sharem. Okay, thanks. In that case I do not know whether the code is compliant and clang has a bug. I believe the standard does not forbid this but on the other hand it is not implementable if one would put the definition inside `.cpp` files. – Quimby Dec 03 '21 at 16:31
  • @n.1.8e9-where's-my-sharem. FWIW I tried moving the template out to an enclosing struct and not surprisingly that didn't help: https://godbolt.org/z/1hfr7csb8 – Ben Dec 03 '21 at 16:40
  • I can wrap an `enum class` in a templated struct and then overload operators. That almost works and is pretty nice. The one thing there is that there's a distinction I don't really understand between something that's explicitly convertible to `bool` and something that's contextually convertible to `bool`: https://godbolt.org/z/Wqn3M3b7K I didn't realize that `enum class B : bool { no, yes };` had different conversion rules than `struct B { bool b; explicit operator bool() const { return b; } };`. – Ben Dec 03 '21 at 16:48
0

I think you can accomplish your goals (supporting ! operators and explicit conversion to bool) without changing your scoped enumerations.

All scoped enumerations appear to support explicit conversion to bool, even if bool isnt' the underlying type

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

You can overload the ! operator for all scoped enums that have underlying booleans with a template and 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);
parktomatomi
  • 3,851
  • 1
  • 14
  • 18
0

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;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
  • Yeah. I'd really like to have it look just like `enum class B : bool { no, yes };`. – Ben Dec 03 '21 at 16:53