stl2 icon indicating copy to clipboard operation
stl2 copied to clipboard

Replace Boolean with convertible_to<bool>

Open CaseyCarter opened this issue 7 years ago • 22 comments

From LWG Kona review of #155. Boolean is an enormously complicated hunk of specification for what is essentially intended to allow people to return integers from comparison operators in addition to bool. LWG made the suggestion to simply define Boolean as:

template <class T>
concept bool Boolean() { return Integral<T>(); }

which achieves the same goal with two lines of spec. At the very least, it's a reasonable idea to apply this simplification for the TS and see if anyone complains.

Proposed Resolution

See P1934.

CaseyCarter avatar Mar 15 '17 20:03 CaseyCarter

That breaks the use of (non-scoped) enums as booleans. Does that matter?

ericniebler avatar Jun 16 '17 16:06 ericniebler

Assigning to myself...

ericniebler avatar Jun 16 '17 19:06 ericniebler

This can be implemented with something like BuiltinImplicitlyConvertibleTo<T, int>, which can be implemented with the help of the rule prohibiting conversion sequences with two user-defined conversions.

ericniebler avatar Jun 16 '17 19:06 ericniebler

Would also outlaw using std::true_type to signify that something always return true.

timsong-cpp avatar Jun 16 '17 21:06 timsong-cpp

Would also outlaw using std::true_type to signify that something always return true.

Have you used that idiom, or seen others using it?

ericniebler avatar Jun 17 '17 17:06 ericniebler

Have you used that idiom, or seen others using it?

I recall reading that someone proposed having operator== on allocators return true_type as an alternative during the discussion that led to allocator_traits::is_always_equal. I suspect that suggestion wasn't invented on the spot.

timsong-cpp avatar Jun 17 '17 17:06 timsong-cpp

The following Boolean concept passes all of cmcstl2's tests unmodified:

namespace detail {
  template <class B>
  constexpr bool boolean_v =
    std::is_integral<B>::value || std::is_enum<B>::value;

  template <MoveConstructible B>
    requires requires(const B& b) { b.operator bool(); }
  constexpr bool boolean_v<B> = true;

  struct implicit_bool {
    implicit_bool(bool);
  };
}

template <class B>
concept bool Boolean =
  detail::boolean_v<decay_t<B>> &&
  requires (const decay_t<B> b) {
    { b } -> detail::implicit_bool;
  };

Thoughts?

EDIT: I observe that just because a class type provides operator bool doesn't necessarily mean the type can be used like a bool. It may = delete its operator! or its logical operators, or subvert the Boolean-ness of the type in other ways.

ericniebler avatar Jun 17 '17 23:06 ericniebler

Doesn't that still allow

enum C {};
void operator&&(C,C);

timsong-cpp avatar Jun 18 '17 00:06 timsong-cpp

Yup.

ericniebler avatar Jun 18 '17 01:06 ericniebler

I observe that just because a class type provides operator bool doesn't necessarily mean the type can be used like a bool.

This is why we have the boolean expression grammar that is the current formulation of Boolean: the fact that a type can convert to T doesn't mean that a type necessarily must convert to T and act like a T when we want it to do so. I'm beginning to think there just isn't any middle ground between the current Boolean and Integral: either we don't admit any types upon which a user can define operator overloads, and Boolean stays simple, or we do admit them and inevitably require the entire grammar to constrain the behavior of operator overloads.

Note that the current (#155) formulation is still underconstrained in that it only specifies the behavior of a single model of Boolean in expressions. "Given const lvalues b1 and b2 of type remove_reference_t<B>, then Boolean<B>() is satisfied if and only if..." says nothing about what happens in:

template<Range Rng1, Range Rng2>
bool f(Rng1&& rng1, Rng2&& rng2) {
  return begin(rng1) == end(rng1) && begin(rng2) == end(rng2);
}

where the && operator is being applied to two potentially different models of Boolean.

CaseyCarter avatar Jun 18 '17 02:06 CaseyCarter

Maybe I'm just chicken, but this (changing Boolean to Integral) feels like too big a change to make this close to release. I feel like punting to IS. Thoughts?

ericniebler avatar Jun 29 '17 23:06 ericniebler

🐔!

Serious response: We don't really have an idea of the impact this will have on existing codebases that want to transition to Ranges. One way to get that idea would be to put it into the TS. A better way might be to leave the TS alone and survey users and implementers: "What impact would it have on your Ranges code if <blank happens>?"

CaseyCarter avatar Jun 29 '17 23:06 CaseyCarter

So, leave Boolean as it currently is and get experience? My only problem with that is that going from Boolean to Integral is a tightening. And making type-checking stricter is harder than going the other direction.

ericniebler avatar Jun 29 '17 23:06 ericniebler

And making type-checking stricter is harder than going the other direction.

Strengthening requirements means implementors of models have to do more work (do I meet the tighter requirements?). Weakening requirements means users of models have to do more work (are the weaker guarantees sufficient for my usage?). My hunch is that there will be more users than implementors of Boolean models so that tightening would have a lesser overall impact.

That said, our historical response to this kind of dilemma is to screw things down as tightly as possible: I'd almost prefer to require exactly bool and relax it only if people scream. I fear that "convertible-ish to bool" will lead to confusion among users who are used to "non-zero means true" when they hit our foo == bool expression requirement and discover that our version of "allowing" int here means only 0 and 1.

CaseyCarter avatar Jun 30 '17 00:06 CaseyCarter

I'd almost prefer to require exactly bool and relax it only if people scream.

I'm way too 🐔 for that. I want Ranges TS to ship in Toronto. I can just imagine the long faces we would get if we brought forward a proposal to change -> Boolean to -> Same<bool>.

ericniebler avatar Jun 30 '17 20:06 ericniebler

I want Ranges TS to ship in Toronto.

Yip. I afraid that if we slip another meeting we'll start to hear "you should rebase on C++17!" Given my belief that there's no reasonable middle ground between Integral and "ridiculous boolean expression grammar", I agree that we should push this to IS or TS2.

CaseyCarter avatar Jun 30 '17 21:06 CaseyCarter

I want Ranges TS to ship in Toronto.

Yes, please.

However, going off N4651, it appears to me that Integral only accepts fundamental integers, while Boolean accepts custom Boolean types.

What is the motivation for changing Boolean to disallow My_bool, and not changing Integral to allow My_int?

cjdb avatar Jun 30 '17 21:06 cjdb

However, going off N4651, it appears to me that Integral only accepts fundamental integers, while Boolean accepts custom Boolean types.

(We're up to N4671 as of this week's pre-meeting mailing, FYI - there are only a few editorial differences.) Yes, those are observations both correct.

What is the motivation for changing Boolean to disallow My_bool, and not changing Integral to allow My_int?

Boolean is a (misguided?) attempt to allow comparison operators to return types convertible to bool inspired by LWG 2114. We've revised it 3 or 4 times over the last two years into its current (still not exactly correct) boolean-expression-grammar-like form. I'm somewhat concerned that it is:

  • painfully awkward to specify
  • costly to concept-check

and that those prices buy us flexibility that mostly will not be used. I made a comment in Kona to the effect that I'm reasonably certain that Boolean only admits bool and integer types restricted to the domain {0, 1} (I missed enums restricted to the domain {0, 1}) and it was suggested that maybe we should simply replace Boolean with Integral.

Changing Integral would require someone to go to the trouble to characterize Integral types, documenting all of the requirements that Integral models satisfy, and deciding which of them are critical to Integral-ness. That would include obvious things like "Integral refines Regular" and more subtle things like "Integrals have only constant-time operations" and "Integrals are cheaper to pass by value than by reference," and "Integrals can be non-type template parameters." (In fairness, the last three are probably all properties that Integral types inherit from Scalar types.)

CaseyCarter avatar Jun 30 '17 21:06 CaseyCarter

more subtle things like "Integrals have only constant-time operations" and "Integrals are cheaper to pass by value than by reference," and "Integrals can be non-type template parameters." (In fairness, the last three are probably all properties that Integral types inherit from Scalar types.)

It's not at all obvious to me that these are desirable, since it would exclude a bigint type.

ericniebler avatar Jul 01 '17 17:07 ericniebler

I'd like to suggest another tact here. Probably my biggest problem with Boolean right now is the likely effect it has on compile times. If we permitted an implementation that dispatched to a trait -- hiding all the complexity behind a template that gets memoized -- then compile times would be vastly improved. We might even require such an implementation.

If we bring forward such a suggestion, it would be nice to have some numbers about if/how much it improves compile times.

ericniebler avatar Jul 05 '17 17:07 ericniebler

LEWG voted in Cologne to replace Boolean with convertible_to<bool>. TODO: bring a paper to Belfast.

ericniebler avatar Aug 13 '19 23:08 ericniebler

LEWG voted in Cologne to replace Boolean with convertible_to<bool>.

I would characterize this as LEWG providing encouragement to bring a proposal to make such a change; this phrasing suggests that LEWG has approved making the change which they of course cannot without a proposal.

CaseyCarter avatar Aug 15 '19 17:08 CaseyCarter