Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 392
康桓瑋 Avatar asked Apr 04 '21 06:04

康桓瑋


2 Answers

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.

like image 150
T.C. Avatar answered Nov 16 '22 12:11

T.C.


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.

like image 28
StoryTeller - Unslander Monica Avatar answered Nov 16 '22 14:11

StoryTeller - Unslander Monica