C++ Logo

std-proposals

Advanced search

Re: [std-proposals] D3666R0 Bit-precise integers

From: Hans Åberg <haberg_1_at_[hidden]>
Date: Tue, 2 Sep 2025 18:58:29 +0200
> On 2 Sep 2025, at 18:36, David Brown <david.brown_at_[hidden]> wrote:
>
> On 02/09/2025 17:59, Hans Åberg wrote:
>>> On 2 Sep 2025, at 16:01, David Brown <david.brown_at_[hidden]> wrote:
>>>
>>> On 02/09/2025 15:24, Hans Åberg wrote:
>>>>> On 2 Sep 2025, at 14:49, David Brown via Std-Proposals <std-proposals_at_[hidden]> wrote:
>>>>>
>>>>> On 02/09/2025 14:24, Hans Åberg via Std-Proposals wrote:
>>>>>>> On 2 Sep 2025, at 14:14, Jan Schultke <janschultke_at_[hidden]> wrote:
>>>>>>>
>>>>>>> You seem to be confusing some mostly unrelated concepts.
>>>>>>>
>>>>>>>>> 1. C does not allow _BitInt(1); should C++ to make generic programming
>>>>>>>>> more comfortable?
>>>>>>>>
>>>>>>>> The ring ℤ/2ℤ of integers modulo 2, also a field, is isomorphic to the Boolean ring 𝔹 having exclusive or as addition and logical conjunction as multiplication.
>>>>>>>>
>>>>>>>> If bool 1+1 is defined to 0, then it is already in C++.
>>>>>>>
>>>>>>> Whether there is some other C++ thing that works mathematically the
>>>>>>> same doesn't say anything about whether _BitInt(1) is valid or should
>>>>>>> be valid. The issue is regarding a specific type.
>>>>>> It could have been defined to be the same as bool.
>>>>>
>>>>> No, it could not. _BitInt(1), if it is to exist, has the two values -1 and 0. Like all signed integer types, arithmetic overflow on it is undefined, and like all _BitInt types, there is no integer promotion. Thus for _BitInt(1), (-1) + (-1) is UB.
>>>>>
>>>>> unsigned _BitInt(1) can hold 0 and 1, has no integer promotion, and has modulo arithmetic behaviour - thus 1 + 1 gives 0 for "unsigned _BitInt(1)".
>>>> Right. I was thinking about the unsigned type only.
>>>
>>> Certainly "unsigned _BitInt(1)" is more akin to how booleans often work in programming languages. It is a 2-element finite field. (bool in C++ is not a field - it has no addition or multiplication operations. "true + true" is the int value 2, while "true * true" is the int value 1.)
>> One would have to make new type, as bool just expresses the behavior of the logical operators on int.
>
> What would you need a new type for? What could you do with it, that you cannot do with bool today, in C or C++ ?

One reason is to avoid common mistakes, like confusing assignment and equality.

> Some changes /do/ make sense, such as the removal of the increment and decrement operators on bool in C++17. I think it might make sense to limit other arithmetic operators on bools, or at least when operating on two bools - the promotion of bool to int can be convenient in some expressions.

The current bool is useful, as there may not be good support on the assembler level for other types.

>
>>>>> Both signed _BitInt(1) and unsigned _BitInt(1) are integer types representing numbers. In C23 (and presumably C++ when it gets _BitInt), these are included in the "standard integer types". "bool", on the other hand, is /not/ a standard integer type in C++ (though it is one in C).
>>>> I get bool true + true = 2 also in C, so it seems it does not have proper arithmetic + in the sense that the results are the same as in conversion back and forth to int.
>>>
>>> That makes no sense. In C, "true" is the integer constant 1 - of type "int". "_Bool" is an unsigned integer type with a range from 0 to 1 inclusive, and with the conversion operation that converting "x" to type "_Bool" is equivalent to assigning the result of "x != 0" (this comparison expression in C is of type "int", not "bool" like in C++).
>>>
>>> In C and C++, if you write an arithmetic expression with values or expressions of type "bool" (or "_Bool" in C, prior to C23), the subexpressions are promoted to "int" before applying the arithmetic operator.
>>>
>>> If you then assign (or convert) the results back to type "bool", it is handled as any other "int" to "bool" conversion - anything that compares not equal to 0 results in the bool value "true" in C++, or the int value 1 in C, while anything that compares equal to 0 is "false" or "0" respectively.
>> So it makes the declaration that _Bool is an unsigned integer type useless, as the arithmetic cannot be accessed.
>
> Arithmetic on _Bool works fine - just like arithmetic on uint8_t works fine, along with any other unsigned integer type smaller than "int". There is the quirk that these promote to signed int, rather than unsigned int, but otherwise you can do unsigned arithmetic on them. The unusual thing about _Bool is how conversion from other types works when you assign to them - then they do not act like the other unsigned integer types. (But unsigned _BitInt(1) conversion is wrapping, like other unsigned integer types.)

On a 1-bit modular arithmetic type, one should have 1+1=0. But I could not do that with the boolean types.

>>>>> Being able to represent two distinct values does not mean a type behaves like "bool".
>>>>>
>>>>>>>> However, in GCC and Clang, bool 1+1 = 1, and I could not see what the standard specifies.
>>>>>>>
>>>>>>> Any nonzero integer is true when converted to bool, so "bool(true +
>>>>>>> true)" is essentially "1 + 1 != 0". bool gets promoted to int before
>>>>>>> any operation.
>>>>>> Apparently, there is no operator+ for bool. I did:
>>>>>> bool a = 1, b = 1, c = a + b;
>>>>>
>>>>> Correct. There is also no "+" operator for char, or short int - other than _BitInt, types smaller than "int" get promoted to "int" before the operation.
>>>> Right.
>>>> The implicit down-conversions, that lose information, get in the way here.
>>>
>>> Integer promotions are value-preserving up-conversions, and do not lose information. (Some other implicit conversions can change values, but not the integer promotions.)
>> It is the down-conversion from int to bool that loses information. Specifically, the conversion from any non-zero value to > 1. It is useful in C, which has a more primitive type system, and hard to change in C++ as it inherits that from C. If the > conversion from int to bool just throws away the higher bits, then the arithmetic comes out right, but would result in unexpected Boolean behavior.
>
> Conversion to a smaller type always loses information - "throw away the higher bits" loses information!
>
> The way conversion to bool works loses /different/ information than you would lose with a modulo reduction to a "normal" 1-bit unsigned integer (such as _BitInt(1) ). But the bool conversion is /vastly/ more useful than such a modulo reduction would be - it tells you if the original value was 0 or not, rather than if it was odd or even.

The problem is really that these conversions are implicit, and one cannot turn off that to make them explicit only where required. One might achieve that with a proper boolean type, but not with all legacy code from C and C++.

So it would lead to a new language design, that might be difficult to make a part of C++.

Received on 2025-09-02 16:58:46