Reputation: 9703
I've been using enum class FooEnabled : bool { no, yes };
as a way to create type-safe bool
s. 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).
Upvotes: 0
Views: 418
Reputation: 4079
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);
Upvotes: 0
Reputation: 19123
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;
};
}
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.
Upvotes: 1
Reputation: 12891
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;
}
Upvotes: 0