Search code examples
c++comparisonc++20c++-concepts

The mystery of C++20 concept boolean-testable


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?


Solution

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