C++20 introduces a comparison concept boolean-testable, but I noticed its italics and the hyphens in the middle, indicating that it is for exposition-only, and since there is no so-called std::boolean_testable
in <concepts>
, we cannot use it in our own code.
What is the purpose of this exposition-only concept? And why is this concept so mysterious?
boolean-testable
came out of LWG's repeated attempts to specify exactly when a type is sufficiently "boolean-ish" to be suitable for use as the result of predicates and comparisons.
At first, the formulations were simply "convertible to bool
" and in C++11 "contextually convertible to bool
", but LWG2114 pointed out that this is insufficient: if the only requirement on something is that it is convertible to bool
, then the only thing you can do with it is to convert it to bool
. You can't write !pred(x)
, or i != last && pred(*i)
, because !
and &&
might be overloaded to do whatever. That will require littering code with explicit bool
casts everywhere.
What the library really meant was "it converts to bool
when we want it to", but that turns out to be really hard to express: we want b1 && b2
to engage the short-circuiting magic of built-in operator &&
, even when b1
and b2
are different "boolean-ish" types. But when looking at the type of b1
in isolation, we have no idea what other "boolean-ish" types may be out there. It's basically impossible to analyze the type in isolation.
Then the Ranges TS came along, and with it an attempt to specify a Boolean
concept. It's an enormously complicated concept - with more than a dozen expression requirements - that still fails to solve the mixed-type comparison problem, and adds its own issues. For instance, it requires bool(b1 == b2)
to be equal to bool(b1) == bool(b2)
and bool(b1 == bool(b2))
, which means that int
doesn't model Boolean
unless it is restricted to the domain {0, 1}
.
With C++20 about to ship, those problems led P1934R0 to propose throwing in the towel: just require the type to model convertible_to<bool>
, and require users to cast it to bool
when needed. That came with its own problems, as P1964R0 pointed out, especially now that we are shipping concepts for public consumption: do we really want to force users to litter their code that are constrained using standard library concepts with casts to bool
? Especially if only a miniscule fraction of users are using pathological types that overload &&
and ||
anyway, and no standard library implementation supports such types?
The final result is boolean-testable
, which is designed to ensure that you can use !
(just once - !!x
is not required to work), &&
and ||
on the result of the predicate/comparison and get the normal semantics (including short-circuiting for &&
and ||
). To solve the mixed-type problem, its specification contains a complex blob of standardese talking about name lookup and template argument deduction and implicit conversion sequences, but it really boils down to "don't be dumb". P1964R2 includes a detailed wording rationale.
Why is it exposition-only? boolean-testable
came really late in the C++20 cycle: LEWG approved P1964's direction on Friday afternoon in Belfast (the November 2019 meeting, one meeting before C++20 shipped), and it is much lower risk to have an exposition-only concept than a named one, especially as there wasn't a lot of motivation for making it public either. Certainly nobody in the LEWG room asked for it to be named.
Its purpose, like all exposition-only concepts, is to simplify the specification in the standard. It's simply a building block for specifying other (potentially user-facing) concepts without needing to repeat the thing the concept models. Of note, it appears in the specification of another exposition-only concept
template<class T, class U>
concept weakly-equality-comparable-with = // exposition only
requires(const remove_reference_t<T>& t,
const remove_reference_t<U>& u) {
{ t == u } -> boolean-testable;
{ t != u } -> boolean-testable;
{ u == t } -> boolean-testable;
{ u != t } -> boolean-testable;
};
weakly-equality-comparable-with
is satisfied for types that overload the comparison operators with a return type that isn't verbatim bool
necessarily. We can still use these expressions to compare objects, and so the standard seeks to reason about them. And it's not an hypothetical, they can appear in the wild. An example from the Palo Alto report:
... One such example is an early version of the QChar class (1.5 and earlier, at least) (Nokia Corporation, 2011).
class QChar { friend int operator==(QChar c1, QChar c2); friend int operator!=(QChar c1, QChar c2); };
We should be able to use this class in our standard algorithms, despite the fact that the operator does not return a bool.
As for your other question
And why is this concept so mysterious?
It isn't. But if one examines it on cppreference alone, one may miss out on context since it may not be easy to cross-reference it there.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With