Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

`bool` in pointer arithmetic: when does the promotion occur, if ever?

58 views
Skip to first unread message

Andrey Tarasevich

unread,
Aug 15, 2022, 1:45:56 PM8/15/22
to
Hello

Sound like a silly question (and it might just as well be), but
nevertheless:

int *a = <whatever>;
bool b = true;
a + b;

How does the binary `+` work in this case? Its operands are an `int *`
and a `bool`. `bool` is an integer type, so the requirements are met and
the expression is valid. Naturally we expect that `bool` to be promoted
to an integer value of 1, and the rest is clear.

But where does it say in the standard that `bool` should get promoted in
this context? There is no global rule requiring an unconditional
promotion, i.e. each operator's specification mandates integral
promotions individually and explicitly. And I don't see integral
promotions mentioned anywhere in [expr.add].

It does mention usual arithmetic conversions (UAC), of course, and UAC
include integral promotions. But UAC are only applicable when _both_
operands have enumeration or arithmetic type. UAC are not applied when
one operand is a pointer. (The definition of UAC does not accommodate
for such possibility.)

So, how does pointer+bool addition work then? When does `true` turn into
1 here?

--
Best regards
Andrey

David Brown

unread,
Aug 15, 2022, 2:09:36 PM8/15/22
to
On 15/08/2022 19:45, Andrey Tarasevich wrote:
> Hello
>
> Sound like a silly question (and it might just as well be), but
> nevertheless:
>
>    int *a = <whatever>;
>    bool b = true;
>    a + b;
>
> How does the binary `+` work in this case? Its operands are an `int *`
> and a `bool`. `bool` is an integer type, so the requirements are met and
> the expression is valid. Naturally we expect that `bool` to be promoted
> to an integer value of 1, and the rest is clear.

Yes.

>
> But where does it say in the standard that `bool` should get promoted in
> this context? There is no global rule requiring an unconditional
> promotion, i.e. each operator's specification mandates integral
> promotions individually and explicitly. And I don't see integral
> promotions mentioned anywhere in [expr.add].
>

My numbering here is from C++20 draft N4860. I don't think anything
relevant has changed in different standards, but of course section
numbers might.

"7.3.6 Integral promotions" says that a "prvalue of type bool can be
converted to a prvalue of type int". This is one of the "integral
promotions".

"7.4 Usual arithmetic conversions" says that integral promotions are
performed on both operands of many binary operators, if they are not
trumped by floating point conversions. There is a footnote saying "As a
consequence, operands of type bool, char8_t, char16_t, char32_t,
wchar_t, or an enumerated type are converted to some integral type."


> It does mention usual arithmetic conversions (UAC), of course, and UAC
> include integral promotions. But UAC are only applicable when _both_
> operands have enumeration or arithmetic type. UAC are not applied when
> one operand is a pointer. (The definition of UAC does not accommodate
> for such possibility.)
>
> So, how does pointer+bool addition work then? When does `true` turn into
> 1 here?
>

Under "7.6.6 Additive operators", it says that the usual arithmetic
conversions are performed on operands of arithmetic or enumeration type
- that includes bool.

Andrey Tarasevich

unread,
Aug 15, 2022, 2:29:48 PM8/15/22
to
On 8/15/2022 11:09 AM, David Brown wrote:
>>
>> So, how does pointer+bool addition work then? When does `true` turn
>> into 1 here?
>>
>
> Under "7.6.6 Additive operators", it says that the usual arithmetic
> conversions are performed on operands of arithmetic or enumeration type
> - that includes bool.

No, no, no... You can't just arbitrarily pluck this part out of the
whole rule. After/if you apply usual arithmetic conversions here, you
are required to continue to follow the spec and complete all steps. You
are required to follow the "Then the following rules are applied to the
promoted operands:" part in 7.4/1.3
(http://eel.is/c++draft/expr.arith.conv#1.3) The specification of usual
arithmetic conversions does not have an "early exit" clause if one
operand is a pointer.

Now, once you start following these "following rules", it becomes clear
that they are not applicable (or defective) if one operand is a pointer.
Firstly, the concept of "[integer conversion] rank" is not applicable to
pointer types. Secondly, (if we ignore the "firstly") these rules
ultimately require us to convert both operands to a common integer type.

Obviously, in pointer+bool contexts operands are NOT converted to a
common integer type. So, no, usual arithmetic conversions do not apply
here. As I already stated, the intent of the standard wording is to say
that usual arithmetic conversions apply only when _both_ operands have
integral or enum type.

--
Best regards,
Andrey

Andrey Tarasevich

unread,
Aug 15, 2022, 2:35:53 PM8/15/22
to
... when both operands have *arithmetic* or enum type.

--
Best regards,
Andrey


David Brown

unread,
Aug 15, 2022, 3:20:35 PM8/15/22
to
Read the paragraphs in the standards, and follow the rules. When you
write "p + b", with "p" being a pointer-to-int and "b" being a bool, you
get the following chain:

"7.6.6 Additive operators" says "The usual arithmetic conversions (7.4)
are performed for operands of arithmetic or enumeration type." The
pointer is not an arithmetic or enumeration type, but the boolean is.

The "7.4 Usual arithmetic conversions" are primarily concerned about two
operands, but the pattern-matching list of "Otherwise, if either operand
is ..." can be applied to a single operand too. Since there are no
floats, we hit "the integral promotions shall be performed on both
operands" - in this case, just the one. None of the rules after that
are triggered.

(Perhaps the "usual arithmetic conversions" are actually applied to both
operands. A pointer would return unscathed by "integral promotions" as
it matches none of the rule triggers there.)

red floyd

unread,
Aug 15, 2022, 4:49:53 PM8/15/22
to
Given the potential ambiguity in standard conformant behavior, it's
probably better to make it explicit...

[going back to Andrey's initial post]

int *a = <whatever>;
bool b = true;
a + static_cast<int>(b);

Andrey Tarasevich

unread,
Aug 15, 2022, 4:59:21 PM8/15/22
to
On 8/15/2022 12:20 PM, David Brown wrote:
>>
>> Obviously, in pointer+bool contexts operands are NOT converted to a
>> common integer type. So, no, usual arithmetic conversions do not apply
>> here. As I already stated, the intent of the standard wording is to
>> say that usual arithmetic conversions apply only when _both_ operands
>> have integral or enum type.
>>
>
> Read the paragraphs in the standards, and follow the rules.  When you
> write "p + b", with "p" being a pointer-to-int and "b" being a bool, you
> get the following chain:

That's exactly what I'm doing.

> "7.6.6 Additive operators" says "The usual arithmetic conversions (7.4)
> are performed for operands of arithmetic or enumeration type."  The
> pointer is not an arithmetic or enumeration type, but the boolean is.
>
> The "7.4 Usual arithmetic conversions" are primarily concerned about two
> operands, but the pattern-matching list of "Otherwise, if either operand
> is ..." can be applied to a single operand too.

That does not mean that you can simply pluck that part out of the whole
rule and nonchalantly discard the rest. (And I feel like I'm repeating
myself.)

> Since there are no
> floats, we hit "the integral promotions shall be performed on both
> operands" - in this case, just the one.  None of the rules after that
> are triggered.

That's not true. And I already covered this above.

Firstly, the "rules after that" attempt to apply the concept of integer
conversion rank to both operands, which is not defined for pointer
types. The C++ standard does not adhere to the principle that "if some
rule is stated nonsensically then it is simply not triggered". Such
rules would be hard defects.

Secondly, in any case these rules cannot be brushed off under "not
triggered" premise because the very last rule in the list is a "sink":
it is not protected by a trigger at all, it is unconditional. If none of
the previous rules are "triggered" then the last rule applies:

http://eel.is/c++draft/expr.arith.conv#1.3.5
- Otherwise, both operands are converted to the unsigned integer type
corresponding to the type of the operand with signed integer type.

What do you expect this to do if one operand is a pointer?

I thought that this problem would be obvious, but apparently I need to
spell out every detail... So, it is is actually me who's telling you to
"read the paragraphs in the standards, and follow the rules".

> (Perhaps the "usual arithmetic conversions" are actually applied to both
> operands.  A pointer would return unscathed by "integral promotions" as
> it matches none of the rule triggers there.)

Yes, integral promotions are safe specifically because of this: every
rule in integral promotions is protected by an "if barrier" and
therefore it leaves pointers unchanged (assuming a pointer can get there
legally).

But the question is not about integral promotions. The question is about
usual arithmetic conversions. Rules of usual arithmetic conversions do
not implement this kind of protection.

--
Best regards,
Andrey

Tim Rentsch

unread,
Aug 15, 2022, 5:36:25 PM8/15/22
to
Just a few comments..

One, I think you are raising a good point.

Two, my guess is that this issue reflects an oversight on the part
of the C++ standards committee. At the very least, assuming there
is a provision in the C++ standard the gives this result, the
reasoning needed is obscure and deserves a note.

Three, an argument could be made (for C++20, n4860) that converting
'true' or 'false' to 1 or 0 is a consequence of the last sentence
of section 7.3 paragraph 1:

A standard conversion sequence will be applied to an expression
if necessary to convert it to a required destination type.

Granted, the reasoning is fraught with ambiguity, and that is never
good. But the 'if necessary' provision gives a lot of latitude.

Keith Thompson

unread,
Aug 15, 2022, 5:57:40 PM8/15/22
to
I think this is an oversight in the standard, and one that would be
fairly straightforward to correct.

The C standard, under "Additive operators", says:

If both operands have arithmetic type, the usual arithmetic
conversions are performed on them.

The corresponding wording in the C++ standard says:

The usual arithmetic conversions are performed for operands of
arithmetic or enumeration type.

The description of the "usual arithmetic conversions" is similar in both
standards, and assumes that both operands are of arithmetic type. I
can't think of any good reason why the "If both operands have arithmetic
type," wording *should* apply in C but not in C++.

It *should* say that if one operand is of pointer type, the other
operand should undergo the integral promotions (but *not* the usual
arithmetic conversions). So if you add a pointer and a bool, the bool
should be converted to int (0 or 1).

--
Keith Thompson (The_Other_Keith) Keith.S.T...@gmail.com
Working, but not speaking, for Philips
void Void(void) { Void(); } /* The recursive call of the void */

David Brown

unread,
Aug 16, 2022, 3:12:37 AM8/16/22
to
On 15/08/2022 22:59, Andrey Tarasevich wrote:
> On 8/15/2022 12:20 PM, David Brown wrote:
>>>
>>> Obviously, in pointer+bool contexts operands are NOT converted to a
>>> common integer type. So, no, usual arithmetic conversions do not
>>> apply here. As I already stated, the intent of the standard wording
>>> is to say that usual arithmetic conversions apply only when _both_
>>> operands have integral or enum type.
>>>
>>
>> Read the paragraphs in the standards, and follow the rules.  When you
>> write "p + b", with "p" being a pointer-to-int and "b" being a bool,
>> you get the following chain:
>
> That's exactly what I'm doing.
>
>> "7.6.6 Additive operators" says "The usual arithmetic conversions
>> (7.4) are performed for operands of arithmetic or enumeration type."
>> The pointer is not an arithmetic or enumeration type, but the boolean is.
>>
>> The "7.4 Usual arithmetic conversions" are primarily concerned about
>> two operands, but the pattern-matching list of "Otherwise, if either
>> operand is ..." can be applied to a single operand too.
>
> That does not mean that you can simply pluck that part out of the whole
> rule and nonchalantly discard the rest. (And I feel like I'm repeating
> myself.)

Yes, you /can/. These sections are written as lists of "otherwise, if
..." rules. Once you've triggered such a rule, the others on the list
/can/ be discarded. If none are triggered, then nothing happens - no
promotions or conversions apply, nor is the code explicitly ruled valid
or invalid.

>
>> Since there are no floats, we hit "the integral promotions shall be
>> performed on both operands" - in this case, just the one.  None of the
>> rules after that are triggered.
>
> That's not true. And I already covered this above.

I agree that "in pointer+bool contexts operands are NOT converted to a
common integer type". But I disagree that this means the paragraphs
about "usual arithmetic conversions" and "integral promotions" don't
apply - the standards explicitly say they do.

>
> Firstly, the "rules after that" attempt to apply the concept of integer
> conversion rank to both operands, which is not defined for pointer
> types. The C++ standard does not adhere to the principle that "if some
> rule is stated nonsensically then it is simply not triggered". Such
> rules would be hard defects.

SFINAE ?

I think the way it is written /is/ defective - it is not clear enough
how these rules apply. I am merely trying to explain how /I/ interpret
them. My interpretation gives almost certainly the same results as the
C++ committee intended, and it matches both the C standards (which are
clearer here) and implementations. But that does not mean it is correct
- there may be other explanations that give the same resulting
behaviour, and it may be that the standard is defective and there are no
consistent readings of the standard here.


>
> Secondly, in any case these rules cannot be brushed off under "not
> triggered" premise because the very last rule in the list is a "sink":
> it is not protected by a trigger at all, it is unconditional. If none of
> the previous rules are "triggered" then the last rule applies:
>
>   http://eel.is/c++draft/expr.arith.conv#1.3.5
>   - Otherwise, both operands are converted to the unsigned integer type
> corresponding to the type of the operand with signed integer type.
>
> What do you expect this to do if one operand is a pointer?

It doesn't get that far. As I interpret this, only the bool gets the
"usual arithmetic conversions" - thus references to "both operands" only
apply to the single operand. After the integral promotions are
performed (turning the bool into int), we get "If both operands have the
same type, no further conversion is needed" and stop there.

>
> I thought that this problem would be obvious, but apparently I need to
> spell out every detail... So, it is is actually me who's telling you to
> "read the paragraphs in the standards, and follow the rules".

I'm sorry - I thought it was /you/ who asked the original question,
looking for help? And the thanks I get for going through the paragraphs
and explaining as best I can how I read the standards, is to get my head
bitten off?

Maybe my understanding of the standards is not quite right. Maybe my
explanation wasn't great. But if this is your idea of how to say "I
don't see it that way, but thanks for trying", I'll leave you to figure
things out some other way. Perhaps someone else will reply to you, and
maybe give you a reading that you'd prefer.

Andrey Tarasevich

unread,
Aug 16, 2022, 12:56:24 PM8/16/22
to
On 8/16/2022 12:12 AM, David Brown wrote:
>>
>> Secondly, in any case these rules cannot be brushed off under "not
>> triggered" premise because the very last rule in the list is a "sink":
>> it is not protected by a trigger at all, it is unconditional. If none
>> of the previous rules are "triggered" then the last rule applies:
>>
>>    http://eel.is/c++draft/expr.arith.conv#1.3.5
>>    - Otherwise, both operands are converted to the unsigned integer
>> type corresponding to the type of the operand with signed integer type.
>>
>> What do you expect this to do if one operand is a pointer?
>
> It doesn't get that far.  As I interpret this, only the bool gets the
> "usual arithmetic conversions" - thus references to "both operands" only
> apply to the single operand.  After the integral promotions are
> performed (turning the bool into int), we get "If both operands have the
> same type, no further conversion is needed" and stop there.
>

But... why? Why do we suddenly stop at "If both operands have the same
type..."? We have a pointer and a bool. How is that "the same type",
even if you promote the bool to an integer?

> I'm sorry - I thought it was /you/ who asked the original question, looking for help? And the thanks I get for going through the paragraphs and explaining as best I can how I read the standards, is to get my head bitten off?
>
> Maybe my understanding of the standards is not quite right. Maybe my explanation wasn't great. But if this is your idea of how to say "I don't see it that way, but thanks for trying", I'll leave you to figure things out some other way. Perhaps someone else will reply to you, and maybe give you a reading that you'd prefer.

Well, this was intended as a "language lawyer" kind of question. So, the
help I was looking for does not allow for interpretations. I just needed
a confirmation that what looks to me as a defect is indeed likely to be
a defect.

--
Best regards,

Alf P. Steinbach

unread,
Aug 16, 2022, 3:13:24 PM8/16/22
to
Looks like a little defect in the standard, including C++17 where there
is contextual implicit conversion. cppreference lists the cases where
that applies, <url:
https://en.cppreference.com/w/cpp/language/implicit_conversion>. No, no
`+` there.

And as far as I can see the problem for `+` is not only `bool` but also
unscoped enumeration arg.

The `+` section (additive operators) states that if one arg is pointer
then the other must be integral type or unscoped enumeration, but it
then defines the effect only for integral type. Gah.

It does invoke usual arithmetic conversions, but that leads down to a
self-contradiction: it's assumed that if the operand types are different
after promotion then they must be unsigned and signed integral.

In short, it's a subtle mess, probably too unimportant for the committee
to fix, or even an editorial fix. I guess Someone(tm) can check if
there's a Defect Report for this. Maybe one was issued already for C++03.


- Alf

Language Lawyer

unread,
Aug 17, 2022, 12:09:00 PM8/17/22
to

Tim Rentsch

unread,
Sep 15, 2022, 10:19:05 AM9/15/22
to
Andrey Tarasevich <andreyta...@hotmail.com> writes:

> On 8/16/2022 12:12 AM, David Brown wrote:
>
>>> Secondly, in any case these rules cannot be brushed off under "not
>>> triggered" premise because the very last rule in the list is a
>>> "sink": it is not protected by a trigger at all, it is
>>> unconditional. If none of the previous rules are "triggered" then
>>> the last rule applies:
>>>
>>> http://eel.is/c++draft/expr.arith.conv#1.3.5
>>> - Otherwise, both operands are converted to the unsigned integer
>>> type corresponding to the type of the operand with signed integer
>>> type.
>>>
>>> What do you expect this to do if one operand is a pointer?
>>
>> It doesn't get that far. As I interpret this, only the bool gets
>> the "usual arithmetic conversions" - thus references to "both
>> operands" only apply to the single operand. After the integral
>> promotions are performed (turning the bool into int), we get "If
>> both operands have the same type, no further conversion is needed"
>> and stop there.
>
> But... why? Why do we suddenly stop at "If both operands have the
> same type..."? We have a pointer and a bool. How is that "the
> same type", even if you promote the bool to an integer?

It occurs to me that the problem here is not with the description
of additive operators (section 7.6.6) but with section 7.4, which
defines the usual arithmetic conversions. What we have in the
case of a pointer and a bool is not two operands but one operand,
that is, just the bool (remember that 7.6.6 p1 says "The usual
arithmetic conversions are performed for operands of arithmetic
or enumeration type", which is just the bool operand in this
case). Section 7.4 is written with the assumption that there
will be two operands being treated, but in this case there is
only one. Thus a solution is to revise 7.4 so it addresses the
more general scenario of converting one or more operands. Here
is a draft of a possible revision:


Many operators that expect one or more operands of arithmetic
or enumeration type cause conversions and yield result types in
a similar way. The purpose is to yield a common type, which is
also the type of the result. This pattern is called the usual
arithmetic conversions, which are defined as follows:

(1.1) -- If any operand is of scoped enumeration type no
conversions are performed; if any other operand does not have
the same type, the expression is ill-formed.

(1.2) -- If any operand is of type long double, all other
operands shall be converted to long double.

(1.3) -- Otherwise, if any operand is double, all other
operands shall be converted to double.

(1.4) -- Otherwise, if any operand is float, all other
operands shall be converted to float.

(1.5) -- Otherwise, the integral promotions shall be
performed on all operands. Then the following rules shall
be applied to the promoted operands:

(1.5.1) -- If all operands have the same type, no further
conversion is needed.

(1.5.2) -- Otherwise, if all operands have signed integer types
or all operands have unsigned integer types, all operands with
a type of lesser integer conversion rank shall be converted to
an operand type having the greatest integer conversion rank.

(1.5.3) -- Otherwise, if an operand that has unsigned integer
type has an integer conversion rank that is greater than or
equal to the rank of the type of all other operands, all other
operands shall be converted to the type of the unsigned integer
operand with the greatest integer conversion rank.

(1.5.4) -- Otherwise, if the type of the operand with signed
integer type can represent all of the values of the type of all
other operands, those operands shall be converted to the type
of the signed integer operand with the greatest integer
conversion rank.

(1.5.5) -- Otherwise, all operands shall be converted to the
unsigned integer type corresponding to the type of an operand
with signed integer type having the greatest integer conversion
rank.

2 If one operand is of enumeration type and any other operand
is of a different enumeration type or a floating-point type,
this behavior is deprecated.

Taking this approach solves the problem for section 7.6.6 and
also anticipates possible future additions to the language that
have more than two operands.

Language Lawyer

unread,
Sep 19, 2022, 6:29:06 AM9/19/22
to
> Section 7.4 is written with the assumption that there will be two operands being treated, but in this case there is only one. Thus a solution is to revise 7.4 so it addresses the more general scenario of converting one or more operands.

Or explicitly say in [expr.add] that the promotion is performed on the integer or enumeration operand, as [expr.unary.op] does (https://timsong-cpp.github.io/cppwp/n4861/expr.unary.op#7.sentence-2, https://timsong-cpp.github.io/cppwp/n4861/expr.unary.op#8.sentence-2, https://timsong-cpp.github.io/cppwp/n4861/expr.unary.op#10.sentence-2)

Tim Rentsch

unread,
Oct 2, 2022, 3:38:04 PM10/2/22
to
Language Lawyer <languag...@gmail.com> writes:

>> Section 7.4 is written with the assumption that there will be two
>> operands being treated, but in this case there is only one. Thus a
>> solution is to revise 7.4 so it addresses the more general scenario
>> of converting one or more operands.
>
> Or explicitly say in [expr.add] that the promotion is performed on
> the integer or enumeration operand,

Seems like a bandaid. I think it would be better to add a case
to the usual arithmetic conversions that covers the case of a
pointer and a non-pointer.

0 new messages