This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Resolved status.
Section: 16.4.4.4 [nullablepointer.requirements], 25.3.5.3 [input.iterators], 25.3.5.7 [random.access.iterators], 27.1 [algorithms.general], 27.8 [alg.sorting], 33.2.1 [thread.req.paramname] Status: Resolved Submitter: Daniel Krügler Opened: 2011-12-09 Last modified: 2022-11-22
Priority: 3
View all issues with Resolved status.
Discussion:
As of 16.4.4.2 [utility.arg.requirements] Table 17/18, the return types of the expressions
a == b
or
a < b
for types satisfying the EqualityComparable or LessThanComparable types, respectively, are required to be "convertible to bool" which corresponds to a copy-initialization context. But several newer parts of the library that refer to such contexts have lowered the requirements taking advantage of the new terminology of "contextually convertible to bool" instead, which corresponds to a direct-initialization context (In addition to "normal" direct-initialization constructions, operands of logical operations as well as if or switch conditions also belong to this special context).
One example for these new requirements are input iterators which satisfy EqualityComparable but also specify that the expressiona != b
shall be just "contextually convertible to bool". The same discrepancy exists for requirement set NullablePointer in regard to several equality-related expressions.
For random access iterators we havea < b contextually convertible to bool
as well as for all derived comparison functions, so strictly speaking we could have a random access iterator that does not satisfy the LessThanComparable requirements, which looks like an artifact to me.
Even if we keep with the existing requirements based on LessThanComparable or EqualityComparable we still would have the problem that some current specifications are actually based on the assumption of implicit convertibility instead of "explicit convertibility", e.g. 20.3.1.6 [unique.ptr.special] p3:template <class T1, class D1, class T2, class D2> bool operator!=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);-3- Returns: x.get() != y.get().
Similar examples exist in 20.3.1.3.3 [unique.ptr.single.dtor] p2, 20.3.1.3.4 [unique.ptr.single.asgn] p9, 20.3.1.3.5 [unique.ptr.single.observers] p1+3+8, etc.
In all these places the expressions involving comparison functions (but not those of the conversion of a NullablePointer to bool!) assume to be "convertible to bool". I think this is a very natural assumption and all delegations of the comparison functions of some type X to some other API type Y in third-party code does so assuming that copy-initialization semantics will just work. The actual reason for using the newer terminology can be rooted back to LWG 556. My hypotheses is that the resolution of that issue also needs a slight correction. Why so? The reason for opening that issue were worries based on the previous "convertible to bool" wording. An expressions like "!pred(a, b)" might not be well-formed in those situations, because operator! might not be accessible or might have an unusual semantics (and similarly for other logical operations). This can indeed happen with unusual proxy return types, so the idea was that the evaluation of Predicate, BinaryPredicate (27.1 [algorithms.general] p8+9), and Compare (27.8 [alg.sorting] p2) should be defined based on contextual conversion to bool. Unfortunately this alone is not sufficient: In addition, I think, we also want the predicates to be (implicitly) convertible to bool! Without this wording, several conditions are plain wrong, e.g. 27.6.6 [alg.find] p2, which talks about "pred(*i) != false" (find_if) and "pred(*i) == false" (find_if_not). These expressions are not within a boolean context! While we could simply fix all these places by proper wording to be considered in a "contextual conversion to bool", I think that this is not the correct solution: Many third-party libraries already refer to the previous C++03 Predicate definition — it actually predates C++98 and is as old as the SGI specification. It seems to be a high price to pay to switch to direct initialization here instead of fixing a completely different specification problem. A final observation is that we have another definition for a Predicate in 33.2.1 [thread.req.paramname] p2:If a parameter is Predicate, operator() applied to the actual template argument shall return a value that is convertible to bool.
The problem here is not that we have two different definitions of Predicate in the standard — this is confusing, but this fact alone is not a defect. The first (minor) problem is that this definition does not properly apply to function objects that are function pointers, because operator() is not defined in a strict sense. But the actually worse second problem is that this wording has the very same problem that has originally lead to LWG 556! We only need to look at 33.7.4 [thread.condition.condvar] p15 to recognice this:
while (!pred()) wait(lock);
The negation expression here looks very familiar to the example provided in LWG 556 and is sensitive to the same "unusual proxy" problem. Changing the 33.2.1 [thread.req.paramname] wording to a corresponding "contextual conversion to bool" wouldn't work either, because existing specifications rely on "convertible to bool", e.g. 33.7.4 [thread.condition.condvar] p32+33+42 or 33.7.5 [thread.condition.condvarany] p25+26+32+33.
To summarize: I believe that LWG 556 was not completely resolved. A pessimistic interpretation is, that even with the current wording based on "contextually convertible to bool" the actual problem of that issue has not been fixed. What actually needs to be required here is some normative wording that basically expresses something along the lines of:The semantics of any contextual conversion to bool shall be equivalent to the semantics of any implicit conversion to bool.
This is still not complete without having concepts, but it seems to be a better approximation. Another way of solving this issue would be to define a minimum requirements table with equivalent semantics. The proposed wording is a bit simpler but attempts to express the same thing.
[2012, Kona]
Agree with Daniel that we potentially broke some C++03 user code, accept the changes striking "contextually" from tables. Stefan to provide revised wording for section 25, and figure out changes to section 30.
Move to open, and then to Review when updated wording from Stefan is available.
[2012-10-12, STL comments]
The current proposed resolution still isn't completely satisfying. It would certainly be possible for the Standard to require these various expressions to be implicitly and contextually convertible to bool, but that would have a subtle consequence (which, I will argue, is undesirable - regardless of the fact that it dates all the way back to C++98/03). It would allow users to provide really wacky types to the Standard Library, with one of two effects:
Standard Library implementations would have to go to great lengths to respect such wacky types, essentially using static_cast<bool> when invoking any predicates or comparators.
Otherwise, such wacky types would be de facto nonportable, because they would make Standard Library implementations explode.
Effect B is the status quo we're living with today. What Standard Library implementations want to do with pred(args) goes beyond "if (pred(args))" (C++03), contextually converting pred(args) to bool (C++11), or implicitly and contextually converting pred(args) to bool (the current proposed resolution). Implementations want to say things like:
if (pred(args)) if (!pred(args)) if (cond && pred(args)) if (cond && !pred(args))
These are real examples taken from Dinkumware's implementation. There are others that would be realistic ("pred(args) && cond", "cond || pred(args)", etc.)
Although negation was mentioned in this issue's Discussion section, and in LWG 556's, the current proposed resolution doesn't fix this problem. Requiring pred(args) to be implicitly and contextually convertible to bool doesn't prevent operator!() from being overloaded and returning std::string (as a wacky example). More ominously, it doesn't prevent operator&&() and operator||() from being overloaded and destroying short-circuiting.I would like LWG input before working on Standardese for a new proposed resolution. Here's an outline of what I'd like to do:
Introduce a new "concept" in 16.4.4 [utility.requirements], which I would call BooleanTestable in the absence of better ideas.
Centralize things and reduce verbosity by having everything simply refer to BooleanTestable when necessary. I believe that the tables could say "Return type: BooleanTestable", while Predicate/BinaryPredicate/Compare would need the incantation "shall satisfy the requirements of BooleanTestable".
Resolve the tug-of-war between users (who occasionally want to do weird things) and implementers (who don't want to have to contort their code) by requiring that:
Given a BooleanTestable x, x is both implicitly and contextually convertible to bool.
Given a BooleanTestable x, !x is BooleanTestable. (This is intentionally "recursive".)
Given a BooleanTestable x, bool t = x, t2(x), f = !x; has the postcondition t == t2 && t != f.
Given a BooleanTestable x and a BooleanTestable y of possibly different types, "x && y" and "x || y" invoke the built-in operator&&() and operator||(), triggering short-circuiting.
bool is BooleanTestable.
I believe that this simultaneously gives users great latitude to use types other than bool, while allowing implementers to write reasonable code in order to get their jobs done. (If I'm forgetting anything that implementers would want to say, please let me know.)
About requirement (I): As Daniel patiently explained to me, we need to talk about both implicit conversions and contextual conversions, because it's possible for a devious type to have both "explicit operator bool()" and "operator int()", which might behave differently (or be deleted, etc.).
About requirement (IV): This is kind of tricky. What we'd like to say is, "BooleanTestable can't ever trigger an overloaded logical operator". However, given a perfectly reasonable type Nice - perhaps even bool itself! - other code (perhaps a third-party library) could overload operator&&(Nice, Evil). Therefore, I believe that the requirement should be "no first use" - the Standard Library will ask for various BooleanTestable types from users (for example, the result of "first != last" and the result of "pred(args)"), and as long as they don't trigger overloaded logical operators with each other, everything is awesome.
About requirement (V): This is possibly redundant, but it's trivial to specify, makes it easier for users to understand what they need to do ("oh, I can always achieve this with bool"), and provides a "base case" for requirement (IV) that may or may not be necessary. Since bool is BooleanTestable, overloading operator&&(bool, Other) (etc.) clearly makes the Other type non-BooleanTestable.
This wording is relative to the FDIS.
Change Table 25 — "NullablePointer requirements" in 16.4.4.4 [nullablepointer.requirements] as indicated:
Table 25 — NullablePointer requirements Expression Return type Operational semantics […] a != b contextuallyconvertible to bool!(a == b) a == np
np == acontextuallyconvertible to boola == P() a != np
np != acontextuallyconvertible to bool!(a == np) Change Table 107 — "Input iterator requirements" in 25.3.5.3 [input.iterators] as indicated:
Table 107 — Input iterator requirements (in addition to Iterator) Expression Return type Operational semantics Assertion/note
pre-/post-conditiona != b contextuallyconvertible to bool!(a == b) pre: (a, b) is in the domain of ==. […] Change Table 111 — "Random access iterator requirements" in 25.3.5.7 [random.access.iterators] as indicated:
Table 111 — Random access iterator requirements (in addition to bidirectional iterator) Expression Return type Operational semantics Assertion/note
pre-/post-condition[…] a < b contextuallyconvertible to boolb - a > 0 < is a total ordering relation a > b contextuallyconvertible to boolb < a > is a total ordering relation opposite to <. a >= b contextuallyconvertible to bool!(a < b) a <= b contextuallyconvertible to bool!(a > b) Change 27.1 [algorithms.general] p8+9 as indicated:
-8- The Predicate parameter is used whenever an algorithm expects a function object (22.10 [function.objects]) that, when applied to the result of dereferencing the corresponding iterator, returns a value testable as true. In other words, if an algorithm takes Predicate pred as its argument and first as its iterator argument, it should work correctly in the construct pred(*first) implicitly or contextually converted to bool (Clause 7.3 [conv]). The function object pred shall not apply any non-constant function through the dereferenced iterator.
-9- The BinaryPredicate parameter is used whenever an algorithm expects a function object that when applied to the result of dereferencing two corresponding iterators or to dereferencing an iterator and type T when T is part of the signature returns a value testable as true. In other words, if an algorithm takes BinaryPredicate binary_pred as its argument and first1 and first2 as its iterator arguments, it should work correctly in the construct binary_pred(*first1, *first2) implicitly or contextually converted to bool (Clause 7.3 [conv]). BinaryPredicate always takes the first iterator's value_type as its first argument, that is, in those cases when T value is part of the signature, it should work correctly in the construct binary_pred(*first1, value) implicitly or contextually converted to bool (Clause 7.3 [conv]). binary_pred shall not apply any non-constant function through the dereferenced iterators.Change 27.8 [alg.sorting] p2 as indicated:
-2- Compare is a function object type (22.10 [function.objects]). The return value of the function call operation applied to an object of type Compare, when implicitly or contextually converted to bool (7.3 [conv]), yields true if the first argument of the call is less than the second, and false otherwise. Compare comp is used throughout for algorithms assuming an ordering relation. It is assumed that comp will not apply any non-constant function through the dereferenced iterator.
Change 33.2.1 [thread.req.paramname] p2 as indicated:
-2-
If a parameter is Predicate, operator() applied to the actual template argument shall return a value that is convertible to boolPredicate is a function object type (22.10 [function.objects]). The return value of the function call operation applied to an object of type Predicate, when implicitly or contextually converted to bool (7.3 [conv]), yields true if the corresponding test condition is satisfied, and false otherwise.
[2014-05-20, Daniel suggests concrete wording based on STL's proposal]
The presented wording follows relatively closely STL's outline with the following notable exceptions:
A reference to BooleanTestable in table "Return Type" specifications seemed very unusual to me and I found no "prior art" for this in the Standard. Instead I decided to follow the usual style to add a symbol with a specific meaning to a specific paragraph that specifies symbols and their meanings.
STL's requirement IV suggested to directly refer to built-in operators && and ||. In my opinion this concrete requirement isn't needed if we simply require that two BooleanTestable operands behave equivalently to two those operands after conversion to bool (each of them).
I couldn't find a good reason to require normatively that type bool meets the requirements of BooleanTestable: My assertion is that after having defined them, the result simply falls out of this. But to make this a bit clearer, I added also a non-normative note to these effects.
[2014-06-10, STL comments]
In the current wording I would like to see changed the suggested changes described by bullet #6:
In 24.2.2.1 [container.requirements.general] p4 undo the suggested change
Then change the 7 occurrences of "convertible to bool" in the denoted tables to "bool".
[2015-05-05 Lenexa]
STL: Alisdair wanted to do something here, but Daniel gave us updated wording.
[2015-07 Telecon]
Alisdair: Should specify we don't break short circuiting.
Ville: Looks already specified because that's the way it works for bool.
Geoffrey: Maybe add a note about the short circuiting.
B2/P2 is somewhat ambiguous. It implies that B has to be both implicitly convertible to bool and contextually convertible to bool.
We like this, just have nits.
Status stays Open.
Marshall to ping Daniel with feedback.
[2016-02-27, Daniel updates wording]
The revised wording has been updated from N3936 to N4567.
To satisfy the Kona 2015 committee comments, the wording in [booleantestable.requirements] has been improved to better separate the two different requirements of "can be contextually converted to bool" and "can be implicitly converted to bool. Both are necessary because it is possible to define a type that has the latter property but not the former, such as the following one:
2016-08-07, Daniel: The below example has been corrected to reduce confusion about the performed conversions as indicated by the delta markers:
using Bool = int; struct OddBoolean { explicit operator bool() const = delete; operator Bool() const;OddBoolean(bool) = delete; OddBoolean(Bool){}} ob; bool b2 = ob; // OK bool b1(ob); // ErrorOddBoolean b2 = true; // OK OddBoolean b1(true); // Error
In [booleantestable.requirements] a note has been added to ensure that an implementation is not allowed to break any short-circuiting semantics.
I decided to separate LWG 2587/2588 from this issue. Both these issues aren't exactly the same but depending on the committee's position, their resolution might benefit from the new vocabulary introduced here.
[2021-06-25; Daniel comments]
The paper P2167R1 is provided to resolve this issue.
[2022-05-01; Daniel comments]
The paper P2167R2 is provided to resolve this issue.
Previous resolution [SUPERSEDED]:
This wording is relative to N4567.
Change 16.4.4.2 [utility.arg.requirements] p1, Table 17 — "EqualityComparable requirements", and Table 18 — "LessThanComparable requirements" as indicated:
-1- […] In these tables, T is an object or reference type to be supplied by a C++ program instantiating a template; a, b, and c are values of type (possibly const) T; s and t are modifiable lvalues of type T; u denotes an identifier; rv is an rvalue of type T;
[…]andv is an lvalue of type (possibly const) T or an rvalue of type const T; and BT denotes a type that meets the BooleanTestable requirements ([booleantestable.requirements]).
Table 17 — EqualityComparable requirements [equalitycomparable] Expression Return type Requirement a == b convertible toBT
bool== is an equivalence relation, that is, it has the following properties: […] […]
Table 18 — LessThanComparable requirements [lessthancomparable] Expression Return type Requirement a < b convertible toBT
bool< is a strict weak ordering relation (27.8 [alg.sorting]) Between 16.4.4.3 [swappable.requirements] and 16.4.4.4 [nullablepointer.requirements] insert a new sub-clause as indicated:
?.?.?.? BooleanTestable requirements [booleantestable.requirements]-?- A BooleanTestable type is a boolean-like type that also supports conversions to bool. A type B meets the BooleanTestable requirements if the expressions described in Table ?? are valid and have the indicated semantics, and if B also satisfies all the other requirements of this sub-clause [booleantestable.requirements].
An object b of type B can be implicitly converted to bool and in addition can be contextually converted to bool (Clause 4). The result values of both kinds of conversions shall be equivalent. [Example: The types bool, std::true_type, and std::bitset<>::reference are BooleanTestable types. — end example] For the purpose of Table ??, let B2 and Bn denote types (possibly both equal to B or to each other) that meet the BooleanTestable requirements, let b1 denote a (possibly const) value of B, let b2 denote a (possibly const) value of B2, and let t1 denote a value of type bool. [Note: These rules ensure what an implementation can rely on but doesn't grant it license to break short-circuiting behavior of a BooleanTestable type. — end note]Somewhere within the new sub-clause [booleantestable.requirements] insert the following new Table (?? denotes the assigned table number):
Table ?? — BooleanTestable requirements [booleantestable] Expression Return type Operational semantics bool(b1) bool Remarks: bool(b1) == t1 for every value
b1 implicitly converted to t1.!b1 Bn Remarks: bool(b1) == !bool(!b1) for
every value b1.b1 && b2 bool bool(b1) && bool(b2) b1 || b2 bool bool(b1) || bool(b2) Change 16.4.4.4 [nullablepointer.requirements] p5 and Table 25 — "NullablePointer requirements" as indicated:
[…]
-5- In Table 25, u denotes an identifier, t denotes a non-const lvalue of type P, a and b denote values of type (possibly const) P,andnp denotes a value of type (possibly const) std::nullptr_t, and BT denotes a type that meets the BooleanTestable requirements ([booleantestable.requirements]). […]
Table 25 — NullablePointer requirements [nullablepointer] Expression Return type Operational semantics … a != b contextually convertible to boolBT[…] a == np
np == acontextually convertible to boolBT[…] a != np
np != acontextually convertible to boolBT[…] Change 22.4.9 [tuple.rel] as indicated;
template<class... TTypes, class... UTypes> constexpr bool operator==(const tuple<TTypes...>& t, const tuple<UTypes...>& u);-1- Requires: For all i, where 0 <= i and i < sizeof...(TTypes), get<i>(t) == get<i>(u) is a valid expression returning a type that
[…]is convertible to boolmeets the BooleanTestable requirements ([booleantestable.requirements]). sizeof...(TTypes) == sizeof...(UTypes).template<class... TTypes, class... UTypes> constexpr bool operator<(const tuple<TTypes...>& t, const tuple<UTypes...>& u);-4- Requires: For all i, where 0 <= i and i < sizeof...(TTypes), get<i>(t) < get<i>(u) and get<i>(u) < get<i>(t) are valid expressions returning types that
[…]are convertible to boolmeet the BooleanTestable requirements ([booleantestable.requirements]). sizeof...(TTypes) == sizeof...(UTypes).Change 24.2.2.1 [container.requirements.general], Table 95 — "Container requirements", and Table 97 — "Optional container operations" as indicated:
-4- In Tables 95, 96, and 97 X denotes a container class containing objects of type T, a and b denote values of type X, u denotes an identifier, r denotes a non-const value of type X,
andrv denotes a non-const rvalue of type X, and BT denotes a type that meets the BooleanTestable requirements ([booleantestable.requirements]).
Table 95 — Container requirements Expression Return type […] … a == b convertible toBT
bool[…] a != b convertible toBT
bool[…] … a.empty() convertible toBT
bool[…] […]
Table 97 — Optional container requirements Expression Return type […] … a < b convertible toBT
bool[…] a > b convertible toBT
bool[…] a <= b convertible toBT
bool[…] a >= b convertible toBT
bool[…] Change 25.3.1 [iterator.requirements.general], Table 106 — "Input iterator requirements", and Table 110 — "Random access iterator requirements" as indicated:
-12- In the following sections, a and b denote values of type X or const X, difference_type and reference refer to the types iterator_traits<X>::difference_type and iterator_traits<X>::reference, respectively, n denotes a value of difference_type, u, tmp, and m denote identifiers, r denotes a value of X&, t denotes a value of value type T, o denotes a value of some type that is writable to the output iterator, and BT denotes a type that meets the BooleanTestable requirements ([booleantestable.requirements]).
Table 106 — Input iterator requirements Expression Return type […] a != b contextually convertible toBT
bool[…] […]
Table 110 — Random access iterator requirements Expression Return type […] … a < b contextually convertible toBT
bool[…] a > b contextually convertible toBT
bool[…] a >= b contextually convertible toBT
bool[…] a <= b contextually convertible toBT
bool[…] Change 27.1 [algorithms.general] p8+p9 as indicated:
[Drafting note: The wording changes below also fix (a) unusual wording forms used ("should work") which are unclear in which sense they are imposing normative requirements and (b) the problem, that the current wording seems to allow that the predicate may mutate a call argument, if that is not a dereferenced iterator. Upon applying the new wording it became obvious that the both the previous and the new wording has the effect that currently algorithms such as adjacent_find, search_n, unique, and unique_copy are not correctly described (because they have no iterator argument named first1), which could give raise to a new library issue. — end drafting note]
-8- The Predicate parameter is used whenever an algorithm expects a function object (20.9) that, when applied to the result of dereferencing the corresponding iterator, returns a value testable as true.
-9- The BinaryPredicate parameter is used whenever an algorithm expects a function object that when applied to the result of dereferencing two corresponding iterators or to dereferencing an iterator and type T when T is part of the signature returns a value testable as true.In other words, iIf an algorithm takes Predicate pred as its argument and first as its iterator argument,it should work correctly in the construct pred(*first) contextually converted to bool (Clause 4)the expression pred(*first) shall have a type that meets the BooleanTestable requirements ( [booleantestable.requirements]). The function object pred shall not apply any non-constant function throughthe dereferenced iteratorits argument.In other words, iIf an algorithm takes BinaryPredicate binary_pred as its argument and first1 and first2 as its iterator arguments,it should work correctly in the construct binary_pred(*first1, *first2) contextually converted to bool (Clause 4)the expression binary_pred(*first1, *first2) shall have a type that meets the BooleanTestable requirements ( [booleantestable.requirements]). BinaryPredicate always takes the first iterator's value_type as its first argument, that is, in those cases when T value is part of the signature,it should work correctly in the construct binary_pred(*first1, value) contextually converted to bool (Clause 4)the expression binary_pred(*first1, value) shall have a type that meets the BooleanTestable requirements ( [booleantestable.requirements]). binary_pred shall not apply any non-constant function throughthe dereferenced iteratorsany of its arguments.Change 27.8 [alg.sorting] p2 as indicated:
[…]
-2- Compare is a function object type (20.9).The return value of the function call operation applied to an object of type Compare, when contextually converted to bool(Clause 4), yields true if the first argument of the call is less than the second, and false otherwise.Compare comp is used throughout for algorithms assuming an ordering relation. Let a and b denote two argument values whose types depend on the corresponding algorithm. Then the expression comp(a, b) shall have a type that meets the BooleanTestable requirements ( [booleantestable.requirements]). The return value of comp(a, b), converted to bool, yields true if the first argument a is less than the second argument b, and false otherwise. It is assumed that comp will not apply any non-constant function throughthe dereferenced iteratorany of its arguments. […]Change 31.5.3.2 [fpos.operations] and Table 126 — "Position type requirements" as indicated:
-1- Operations specified in Table 126 are permitted. In that table,
P refers to an instance of fpos,
[…]
o refers to a value of type streamoff,
BT refers to a type that meets the BooleanTestable requirements ([booleantestable.requirements]),
[…]
Table 126 — Position type requirements Expression Return type […] … p == q convertible to boolBT[…] p != q convertible to boolBT[…] Change 33.2.1 [thread.req.paramname] p1 as indicated:
-1- Throughout this Clause, the names of template parameters are used to express type requirements.
If a template parameter is named Predicate, operator() applied to the template argument shall return a value that is convertible to boolPredicate is a function object type (22.10 [function.objects]). Let pred denote an lvalue of type Predicate. Then the expression pred() shall have a type that meets the BooleanTestable requirements ( [booleantestable.requirements]). The return value of pred(), converted to bool, yields true if the corresponding test condition is satisfied, and false otherwise.
[2022-11-05; Daniel comments]
The paper P2167R3 has been reviewed and accepted by LWG and would solve this issue.
[2022-11-22 Resolved by P2167R3 accepted in Kona. Status changed: Open → Resolved.]
Proposed resolution: