ericniebler / stl2

LaTeX and Markdown source for the Ranges TS/STL2 and associated proposals
88 stars 8 forks source link

Replace Boolean with convertible_to<bool> #389

Open CaseyCarter opened 7 years ago

CaseyCarter commented 7 years ago

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.

ericniebler commented 7 years ago

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

ericniebler commented 7 years ago

Assigning to myself...

ericniebler commented 7 years ago

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.

timsong-cpp commented 7 years ago

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

ericniebler commented 7 years ago

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

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

timsong-cpp commented 7 years ago

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.

ericniebler commented 7 years ago

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.

timsong-cpp commented 7 years ago

Doesn't that still allow

enum C {};
void operator&&(C,C);
ericniebler commented 7 years ago

Yup.

CaseyCarter commented 7 years ago

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.

ericniebler commented 7 years ago

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?

CaseyCarter commented 7 years ago

🐔!

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>?"

ericniebler commented 7 years ago

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.

CaseyCarter commented 7 years ago

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.

ericniebler commented 7 years ago

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

CaseyCarter commented 7 years ago

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.

cjdb commented 7 years ago

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?

CaseyCarter commented 7 years ago

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:

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

ericniebler commented 7 years ago

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 commented 7 years ago

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 commented 5 years ago

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

CaseyCarter commented 5 years ago

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.