stl2
stl2 copied to clipboard
Replace Boolean with convertible_to<bool>
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.
That breaks the use of (non-scoped) enums as booleans. Does that matter?
Assigning to myself...
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.
Would also outlaw using std::true_type
to signify that something always return true.
Would also outlaw using
std::true_type
to signify that something always return true.
Have you used that idiom, or seen others using it?
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.
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.
Doesn't that still allow
enum C {};
void operator&&(C,C);
Yup.
I observe that just because a class type provides
operator bool
doesn't necessarily mean the type can be used like abool
.
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
.
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?
🐔!
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>?"
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.
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
.
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>
.
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.
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
?
However, going off N4651, it appears to me that
Integral
only accepts fundamental integers, whileBoolean
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 disallowMy_bool
, and not changingIntegral
to allowMy_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 "Integral
s have only constant-time operations" and "Integral
s are cheaper to pass by value than by reference," and "Integral
s can be non-type template parameters." (In fairness, the last three are probably all properties that Integral
types inherit from Scalar
types.)
more subtle things like "
Integral
s have only constant-time operations" and "Integral
s are cheaper to pass by value than by reference," and "Integral
s 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.
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.
LEWG voted in Cologne to replace Boolean
with convertible_to<bool>
. TODO: bring a paper to Belfast.
LEWG voted in Cologne to replace
Boolean
withconvertible_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.