Forcing sign of a bit field (pre-C++14) when using fixed size types - c++

Skip to the bolded part for the essential question, the rest is just background.
For reasons I prefer not to get into, I'm writing a code generator that generates C++ structs in a (very) pre-C++14 environment. The generator has to create bit-fields; it also needs the tightest possible control over the behaviour of the generated fields, in as portable a fashion as possible. I need to control both the size of the underlying allocation unit, and how signed values are handled. I won't get into why I'm on such a fool's errand, that so obviously runs afoul of Implementation Defined behaviour, but there's a paycheck involved, and all the right ways to do what needs to be done have been rejected by the people who arrange the paychecks.
So I'm stuck generating things like:
int32_t x : 11;
because I need to convince the compiler that this field (and other adjacent fields with the same underlying type) live in a 32 bit word. Generating int for the underlying type is not an option because int doesn't have a fixed size, and things would go very wrong the day someone releases a compiler in which int is 64 bits wide, or we end up back on one where it's 16.
In pre-C++14, int x: 11 might or might not be an unsigned field, and you prepend an explicit signed or unsigned to get what you need. I'm concerned that int32_t and friends will have the same ambiguity (why wouldn't it?) but compilers are gagging on signed int32_t.
Does the C++ standard have any words on whether the intxx_t types impose their signedness on bit fields? If not, is there any guarantee that something like
typedef signed int I32;
...
I32 x : 11;
...
assert(sizeof(I32)==4); //when this breaks, you won't have fun
will carry the signed indicator into the bitfield?
Please note that any suggestion that starts with "just generate a function to..." is by fiat off the table. These generated headers will be plugged into code that does things like s->x = 17; and I've had it nicely explained to me that I must not suggest changing it all to s->set_x(17) even one more time. Even though I could trivially generate a set_x function to exactly and safely do what I need without any implementation defined behaviour at all. Also, I've very aware of the vagaries of bit fields, and left to right and right to left and inside out and whatever else compilers get up to with them, and several other reasons why this is a fool's errand. And I can't just "try stuff" because this needs to work on compilers I don't have, which is why I'm scrambling after guarantees in the standard.
Note: I can't implement any solution that doesn't allow existing code to simply cast a pointer to a buffer of bytes to a pointer to the generated struct, and then use their pointer to get to fields to read and write. The existing code is all about s->x, and must work with no changes. That rules out any solution involving a constructor in generated code.

Does the C++ standard have any words on whether the intxx_t types impose their signedness on bit fields?
No.
The standard's synopsis for the fixed-width integers of <cstdint>, [cstdint.syn] (link to modern standard; the relevant parts of the synopsis looks the same in the C++11 standard) simply specifies, descriptively (not by means of the signed/unsigned keywords), that they shall be of "signed integer type" or "unsigned integer type".
E.g. for gcc, <cstdint> expose the fixed width integers of <stdint.h>, which in turn are typedefs to predefined pre-processor macros (e.g. __INT32_TYPE__ for int32_t), the latter being platform specific.
The standard does not impose any required use of the signed or unsigned keywords in this synopsis, and thus bit fields of fixed width integer types will, in C++11, suffer the same implementation-defined behavior regarding their signedness as is present when declaring a plain integer bit field. Recall that the relevant part of [class.bit]/3 prior to C++14 was (prior to action due to CWG 739):
It is implementation-defined whether a plain (neither explicitly signed nor unsigned) char, short, int, long, or long long bit-field is signed or unsigned. ...
Indeed, the following thread
How are the GNU C preprocessor predefined macros used?
shows an example where e.g. __INT32_TYPE__ on the answerer's particular platform is defined with no explicit presence of the signed keyword:
$ gcc -dM -E - < /dev/null | grep __INT
...
#define __INT32_TYPE__ int

it also needs the tightest possible control over the behaviour of the generated fields, in as portable a fashion as possible. I need to control both the size of the underlying allocation unit, and how signed values are handled.
These two goals are incompatible. Bitfields inherently have portability problems.
If the standard defined the behaviors you want, then the "vagaries of bit fields" wouldn't exist, and people wouldn't bother recommending using bitmasks and shifts for portability.
What you possibly could do is to provide a class that exposes the same interface as a struct with bitfields but that doesn't actually use bitfields internally. Then you could make its constructor and destructor read or write those fields portably via masks and shifts. For example, something like:
class BitfieldProxy
{
public:
BitfieldProxy(uint32_t& u)
: x((u >> 4) & 0x7FF),
y(u & 0xF),
mDest(u)
{
}
~BitfieldProxy()
{
assert((x & 0x7FF) == x);
assert((y & 0xF) == y);
dest = (x << 4) | y;
}
BitfieldProxy(const BitfieldProxy&) = delete;
BitfieldProxy& operator=(const BitfieldProxy&) = delete;
// Only the last 11 bits are valid.
unsigned int x;
// Only the last 4 bits are valid.
unsigned int y;
private:
uint32_t& mDest;
};

Related

Compiler optimizations allowed via "int", "least" and "fast" non-fixed width types C/C++

Clearly, fixed-width integral types should be used when the size is important.
However, I read (Insomniac Games style guide), that "int" should be preferred for loop counters / function args / return codes / ect when the size isn't important - the rationale given was that fixed-width types can preclude certain compiler optimizations.
Now, I'd like to make a distinction between "compiler optimization" and "a more suitable typedef for the target architecture". The latter has global scope, and my guess probably has very limited impact unless the compiler can somehow reason about the global performance of the program parameterized by this typedef. The former has local scope, where the compiler would have the freedom to optimize number of bytes used, and operations, based on local register pressure / usage, among other things.
Does the standard permit "compiler optimizations" (as we've defined) for non-fixed-width types? Any good examples of this?
If not, and assuming the CPU can operate on smaller types as least as fast as larger types, then I see no harm, from a performance standpoint, of using fixed-width integers sized according to local context. At least that gives the possibility of relieving register pressure, and I'd argue couldn't be worse.
The reason that the rule of thumb is to use an int is that the standard defines this integral type as the natural data type of the CPU (provided that it is sufficiently wide for the range INT_MIN to INT_MAX. That's where the best-performance stems from.
There are many things wrong with int_fast types - most notably that they can be slower than int!
#include <stdio.h>
#include <inttypes.h>
int main(void) {
printf("%zu\n", sizeof (int_fast32_t));
}
Run this on x86-64 and it prints 8... but it makes no sense - using 64-bit registers often require prefixes in x86-64 bit mode, and the "behaviour on overflow is undefined" means that using 32-bit int it doesn't matter if the upper 32 bits of the 64 bit register are set after arithmetic - the behaviour is "still correct".
What is even worse, however, than using the signed fast or least types, is using a small unsigned integer instead of size_t or a signed integer for a loop counter - now the compiler must generate extra code to "ensure the correct wraparound behaviour".
I'm not very familiar with the x86 instruction set but unless you can guarantee that practically every arithmetic and move instruction also allows additional shift and (sign) extends then the assumption that smaller types are "as least as fast" as larger ones is not true.
The complexity of x86 makes it pretty hard to come up with simple examples so lets consider an ARM microcontroller instead.
Lets define two addition functions which only differ by return type. "add32" which returns an integer of full register width and "add8" which only returns a single byte.
int32_t add32(int32_t a, int32_t b) { return a + b; }
int8_t add8(int32_t a, int32_t b) { return a + b; }
Compiling those functions with -Os gives the following assembly:
add32(int, int):
add r0, r0, r1
bx lr
add8(int, int):
add r0, r0, r1
sxtb r0, r0 // Sign-extend single byte
bx lr
Notice how the function which only returns a byte is one instruction longer. It has to truncate the 32bit addition to a single byte.
Here is a link to the code # compiler explorer:
https://godbolt.org/z/ABFQKe
However, I read (Insomniac Games style guide), that "int" should be preferred for loop counters
You should rather be using size_t, whenever iterating over an array. int has other problems than performance, such as being signed and also problematic when porting.
From a standard point-of-view, for a scenario where "n" is the size of an int, there exists no case where int_fastn_t should perform worse than int, or the compiler/standard lib/ABI/system has a fault.
Does the standard permit "compiler optimizations" (as we've defined) for non-fixed-width types? Any good examples of this?
Sure, the compiler might optimize the use of integer types quite wildly, as long as it doesn't affect the outcome of the result. No matter if they are int or int32_t.
For example, an 8 bit CPU compiler might optimize int a=1; int b=1; ... c = a + b; to be performed on 8 bit arithmetic, ignoring integer promotions and the actual size of int. It will however most likely have to allocate 16 bits of memory to store the result.
But if we give it some rotten code like char a = 0x80; int b = a >> 1;, it will have to do the optimization so that the side affects of integer promotion are taken in account. That is, the result could be 0xFFC0 rather than 0x40 as one might have expected (assuming signed char, 2's complement, arithmetic shift). The a >> 1 part isn't possible to optimize to an 8 bit type because of this - it has to be carried out with 16 bit arithmetic.
I think the question you are trying to ask is:
Is the compiler allowed to make additional optimizations for a
non-fixed-width type such as int beyond what it would be allowed for
a fixed width type like int32_t that happens to have the same
length on the current platform?
That is, you are not interested in the part where the size of the non-fixed width type is allowed to be chosen appropriately for the hardware - you are aware of that and are asking if beyond that additional optimizations are available?
The answer, as far as I am aware or have seen, is no. No both in the sense that compilers do not actually optimize int differently than int32_t (on platforms where int is 32-bits), and also no in the sense that there are not optimizations allowed by the standard for int which are also not allowed for int32_t1 (this second part is wrong - see comments).
The easiest way to see this is that the various fixed width integers are all typedefs for various underlying primitive integer types - so on a platform with 32-bit integers int32_t will probably be a typedef (perhaps indirectly) of int. So from a behavioral and optimization point of view, the types are identical, and as soon as you are in the IR world of the compiler, the original type probably isn't even really available without jumping through oops (i.e., int and int32_t will generate the same IR).
So I think the advice you received was wrong, or at best misleading.
1 Of course the answer to the question of "Is it allowed for a compiler to optimize int better than int32_t is yes, since there are not particular requirements on optimization so a compiler could do something weird like that, or the reverse, such as optimizing int32_t better than int. I that's not very interesting though.

Purpose of using UINT64_C?

I found this line in boost source:
const boost::uint64_t m = UINT64_C(0xc6a4a7935bd1e995);
I wonder what is the purpose of using a MACRO here?
All this one does is to add ULL to the constant provided.
I assume it may be used to make it harder for people to make mistake of typing UL instead of ULL, but I wonder if there is any other reason to use it.
If you look at boost/cstdint.h, you can see that the definition of the UINT64_C macro is different on different platforms and compilers.
On some platforms it's defined as value##uL, on others it's value##uLL, and on yet others it's value##ui64. It all depends on the size of unsigned long and unsigned long long on that platform or the presence of compiler-specific extensions.
I don't think using UINT64_C is actually necessary in that context, since the literal 0xc6a4a7935bd1e995 would already be interpreted as a 64-bit unsigned integer. It is necessary in some other context though. For example, here the literal 0x00000000ffffffff would be interpreted as a 32-bit unsigned integer if it weren't specifically specified as a 64-bit unsigned integer by using UINT64_C (though I think it would be promoted to uint64_t for the bitwise AND operation).
In any case, explicitly declaring the size of literals where it matters serves a valuable role in code-clarity. Sometimes, even if an operation is perfectly well-defined by the language, it can be difficult for a human programmer to tell what types are involved. Saying it explicitly can make code easier to reason about, even if it doesn't directly alter the behavior of the program.

C++ vector::size_type: signed vs unsigned; int vs. long

I have been doing some testing of my application by compiling it on different platforms, and the shift from a 64-bit system to a 32-bit system is exposing a number of issues.
I make heavy use of vectors, strings, etc., and as such need to count them. However, my functions also make use of 32-bit unsigned numbers because in many cases I need to explicitly consume a positive integer.
I'm having issues with seemingly simple tasks such as std::min and std::max, which may be more systemic. Consider the following code:
uint32_t getmax()
{
return _vecContainer.size();
}
Seems simple enough: I know that a vector can't have a negative number of elements, so returning an unsigned integer makes complete sense.
void setRowCol(const uint32_t &r_row; const uint32_t &r_col)
{
myContainer_t mc;
mc.row = r_row;
mc.col = r_col;
_vecContainer.push_back(mc);
}
Again, simple enough.
Problem:
uint32_t foo(const uint32_t &r_row)
{
return std::min(r_row, _vecContainer.size());
}
This gives me errors such as:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/algorithm:2589:1: note: candidate template ignored: deduced conflicting types for parameter '_Tp' ('unsigned long' vs. 'unsigned int')
min(const _Tp& __a, const _Tp& __b)
I did a lot of digging, and on one platform vector::size_type is an 8 byte number. However, by design I am using unsigned 4-byte numbers. This is presumably causing things to be wacky because you cannot implicitly convert from an 8-byte number to a 4-byte number.
The solution was to do this the old fashioned weay:
#define MIN_M(a,b) a < b ? a : b
return MIN_M(r_row, _vecContainer.size());
Which works dandy. But the systemic issue remains: when planning for multiple platform support, how do you handle instances like this? I could use size_t as my standard size, but that adds other complications (e.g. moving from one platform which supports 64 bit numbers to another which supports 32 bit numbers at a later date). The bigger issue is that size_t is unsigned, so I can't update my signatures:
size_t foo(const size_t &r_row)
// bad, this allows -1 to be passed, which I don't want
Any suggestions?
EDIT: I had read somewhere that size_t was signed, and I've since been corrected. So far it looks like this is a limitation of my own design (e.g. 32-bit numbers vs. using std::vector::size_type and/or size_t).
One way to deal with this is to use
std::vector<Type>::size_type
as the underlying type of your function parameters/returns, or auto returns if using C++14.
An answer in the form of a set of tidbits:
Instead of relying on the compiler to deduce the type, you can explicitly specify the type when using function templates like std::min<T>. For example: std::min<std::uint32_t>(4, my_vec.size());
Turn on all the compiler warnings related to signed versus unsigned comparisons and implicit narrowing conversions. Use brace initialization where you can, as it will treat narrowing conversions as errors.
If you explicitly want to use 32-bit values like std::uint32_t, I'd try to find the minimal number of places to explicitly convert (i.e., static_cast) the "sizes" to the smaller types. You don't want casts everywhere, but if you're using library container sizes internally and you want your API to use std::uint32_t, explicitly cast at the API boundaries so that a user of your class never has to worry about doing the conversion themselves. If you can keep the conversions to just a couple places, it becomes practical to add run-time checks (i.e., assertions) that the size has not actually outgrown the range of the smaller type.
If you don't care about the exact size, use std::size_t, which is almost certainly identical to std::XXX::size_type for all of the standard containers. It's theoretically possible for them to be different, but it doesn't happen in practice. In most contexts, std::size_t is less verbose that std::vector::size_type, so it makes a good compromise.
Lots of people (including many people on the C++ standards committee) will tell you to avoid unsigned values even for sizes and indexes. I understand and respect their arguments, but I don't find them persuasive enough to justify the extra friction at the interface with the standard library. Whether or not it's an historical artifact that std::size_t is unsigned, the fact is that the standard library uses unsigned sizes extensively. If you use something else, your code ends up littered with implicit conversions, all of which are potential bugs. Worse, those implicit conversions make turning on the compiler warnings impractical, so all those latent bugs remain relatively invisible. (And even if you know your sizes will never exceed the smaller type, being forced to turn of the compiler warnings for signedness and narrowing means you could miss bugs in completely unrelated parts of the code.) Match the types of the APIs you're using as much as possible, assert and explicitly convert when necessary, and turn on all the warnings.
Keep in mind that auto is not a panacea. for (auto i = 0; i < my_vec.size(); ++i) ... is just as bad as for (int i .... But if you generally prefer algorithms and iterators to raw loops, auto will get you pretty far.
With division you must never divide unless you know the denominator is not 0. Similarly, with unsigned integral types, you must never subtract unless you know the subtrahend is smaller than or equal to the original value. If you can make that a habit, you can avoid the bugs that the always-use-a-signed-type folks are concerned about.

fixed length data types in C/C++

I've heard that size of data types such as int may vary across platforms.
My first question is: can someone bring some example, what goes wrong, when program
assumes an int is 4 bytes, but on a different platform it is say 2 bytes?
Another question I had is related. I know people solve this issue with some typedefs,
like you have variables like u8,u16,u32 - which are guaranteed to be 8bits, 16bits, 32bits, regardless of the platform -- my question is, how is this achieved usually? (I am not referring to types from stdint library - I am curious manually, how can one enforce that some type is always say 32 bits regardless of the platform??)
I know people solve this issue with some typedefs, like you have variables like u8,u16,u32 - which are guaranteed to be 8bits, 16bits, 32bits, regardless of the platform
There are some platforms, which have no types of certain size (like for example TI's 28xxx, where size of char is 16 bits). In such cases, it is not possible to have an 8-bit type (unless you really want it, but that may introduce performance hit).
how is this achieved usually?
Usually with typedefs. c99 (and c++11) have these typedefs in a header. So, just use them.
can someone bring some example, what goes wrong, when program assumes an int is 4 bytes, but on a different platform it is say 2 bytes?
The best example is a communication between systems with different type size. Sending array of ints from one to another platform, where sizeof(int) is different on two, one has to take extreme care.
Also, saving array of ints in a binary file on 32-bit platform, and reinterpreting it on a 64-bit platform.
In earlier iterations of the C standard, you generally made your own typedef statements to ensure you got a (for example) 16-bit type, based on #define strings passed into the compiler for example:
gcc -DINT16_IS_LONG ...
Nowadays (C99 and above), there are specific types such as uint16_t, the exactly 16-bit wide unsigned integer.
Provided you include stdint.h, you get exact bit width types,at-least-that-width types, fastest types with a given minimum widthand so on, as documented in C99 7.18 Integer types <stdint.h>. If an implementation has compatible types, they are required to provide these.
Also very useful is inttypes.h which adds some other neat features for format conversion of these new types (printf and scanf format strings).
For the first question: Integer Overflow.
For the second question: for example, to typedef an unsigned 32 bits integer, on a platform where int is 4 bytes, use:
typedef unsigned int u32;
On a platform where int is 2 bytes while long is 4 bytes:
typedef unsigned long u32;
In this way, you only need to modify one header file to make the types cross-platform.
If there are some platform-specific macros, this can be achieved without modifying manually:
#if defined(PLAT1)
typedef unsigned int u32;
#elif defined(PLAT2)
typedef unsigned long u32;
#endif
If C99 stdint.h is supported, it's preferred.
First of all: Never write programs that rely on the width of types like short, int, unsigned int,....
Basically: "never rely on the width, if it isn't guaranteed by the standard".
If you want to be truly platform independent and store e.g. the value 33000 as a signed integer, you can't just assume that an int will hold it. An int has at least the range -32767 to 32767 or -32768 to 32767 (depending on ones/twos complement). That's just not enough, even though it usually is 32bits and therefore capable of storing 33000. For this value you definitively need a >16bit type, hence you simply choose int32_t or int64_t. If this type doesn't exist, the compiler will tell you the error, but it won't be a silent mistake.
Second: C++11 provides a standard header for fixed width integer types. None of these are guaranteed to exist on your platform, but when they exists, they are guaranteed to be of the exact width. See this article on cppreference.com for a reference. The types are named in the format int[n]_t and uint[n]_t where n is 8, 16, 32 or 64. You'll need to include the header <cstdint>. The C header is of course <stdint.h>.
usually, the issue happens when you max out the number or when you're serializing. A less common scenario happens when someone makes an explicit size assumption.
In the first scenario:
int x = 32000;
int y = 32000;
int z = x+y; // can cause overflow for 2 bytes, but not 4
In the second scenario,
struct header {
int magic;
int w;
int h;
};
then one goes to fwrite:
header h;
// fill in h
fwrite(&h, sizeof(h), 1, fp);
// this is all fine and good until one freads from an architecture with a different int size
In the third scenario:
int* x = new int[100];
char* buff = (char*)x;
// now try to change the 3rd element of x via buff assuming int size of 2
*((int*)(buff+2*2)) = 100;
// (of course, it's easy to fix this with sizeof(int))
If you're using a relatively new compiler, I would use uint8_t, int8_t, etc. in order to be assure of the type size.
In older compilers, typedef is usually defined on a per platform basis. For example, one may do:
#ifdef _WIN32
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
// and so on...
#endif
In this way, there would be a header per platform that defines specifics of that platform.
I am curious manually, how can one enforce that some type is always say 32 bits regardless of the platform??
If you want your (modern) C++ program's compilation to fail if a given type is not the width you expect, add a static_assert somewhere. I'd add this around where the assumptions about the type's width are being made.
static_assert(sizeof(int) == 4, "Expected int to be four chars wide but it was not.");
chars on most commonly used platforms are 8 bits large, but not all platforms work this way.
Well, first example - something like this:
int a = 45000; // both a and b
int b = 40000; // does not fit in 2 bytes.
int c = a + b; // overflows on 16bits, but not on 32bits
If you look into cstdint header, you will find how all fixed size types (int8_t, uint8_t, etc.) are defined - and only thing differs between different architectures is this header file. So, on one architecture int16_tcould be:
typedef int int16_t;
and on another:
typedef short int16_t;
Also, there are other types, which may be useful, like: int_least16_t
If a type is smaller than you think then it may not be able to store a value you need to store in it.
To create a fixed size types you read the documentation for platforms to be supported and then define typedefs based on #ifdef for the specific platforms.
can someone bring some example, what goes wrong, when program assumes an int is 4 bytes, but on a different platform it is say 2 bytes?
Say you've designed your program to read 100,000 inputs, and you're counting it using an unsigned int assuming a size of 32 bits (32-bit unsigned ints can count till 4,294,967,295). If you compile the code on a platform (or compiler) with 16-bit integers (16-bit unsigned ints can count only till 65,535) the value will wrap-around past 65535 due to the capacity and denote a wrong count.
Compilers are responsible to obey the standard. When you include <cstdint> or <stdint.h> they shall provide types according to standard size.
Compilers know they're compiling the code for what platform, then they can generate some internal macros or magics to build the suitable type. For example, a compiler on a 32-bit machine generates __32BIT__ macro, and previously it has these lines in the stdint header file:
#ifdef __32BIT__
typedef __int32_internal__ int32_t;
typedef __int64_internal__ int64_t;
...
#endif
and you can use it.
bit flags are the trivial example. 0x10000 will cause you problems, you can't mask with it or check if a bit is set in that 17th position if everything is being truncated or smashed to fit into 16-bits.

Why certain implicit type conversions are safe on a machine and not on an other?? How can I prevent this cross platform issues?

I recently found a bug on my code that took me a few hours to debug.
the problem was in a function defined as:
unsigned int foo(unsigned int i){
long int v[]={i-1,i,i+1} ;
.
.
.
return x ; // evaluated by the function but not essential how for this problem.
}
The definition of v didn't cause any issue on my development machine (ubuntu 12.04 32 bit, g++ compiler), where the unsigned int were implicitly converted to long int and as such the negative values were correctly handled.
On a different machine (ubuntu 12.04 64 bit, g++ compiler) however this operation was not safe. When i=0, v[0] was not set to -1, but to some weird big value (as it often happens
when trying to make an unsigned int negative).
I could solve the issue casting the value of i to long int
long int v[]={(long int) i - 1, (long int) i, (long int) i + 1};
and everything worked fine (on both machines).
I can't figure out why the first works fine on a machine and doesn't work on the other.
Can you help me understanding this, so that I can avoid this or other issues in the future?
For unsigned values, addition/subtraction is well-defined as modulo arithmetic, so 0U-1 will work out to something like std::numeric_limits<unsigned>::max().
When converting from unsigned to signed, if the destination type is large enough to hold all the values of the unsigned value then it simply does a straight data copy into the destination type. If the destination type is not large enough to hold all the unsigned values I believe that it's implementation defined (will try to find standard reference).
So when long is 64-bit (presumably the case on your 64-bit machine) the unsigned fits and is copied straight.
When long is 32-bits on the 32-bit machine, again it most likely just interprets the bit pattern as a signed value which is -1 in this case.
EDIT: The simplest way to avoid these problems is to avoid mixing signed and unsigned types. What does it mean to subtract one from a value whose concept doesn't allow for negative numbers? I'm going to argue that the function parameter should be a signed value in your example.
That said g++ (at least version 4.5) provides a handy -Wsign-conversion that detects this issue in your particular code.
You can also have specialized cast catching all over-flow casts:
template<typename O, typename I>
O architecture_cast(I x) {
/* make sure I is an unsigned type. It */
static_assert(std::is_unsigned<I>::value, "Input value to architecture_cast has to be unsigned");
assert(x <= static_cast<typename std::make_unsigned<O>::type>( std::numeric_limits<O>::max() ));
return static_cast<O>(x);
}
Using this will catch in debug all of the casts from bigger numbers than the resulting type can accommodate. This includes your case of unsigned int being 0 and subtracted by -1 which results to biggest unsigned int.
Integer promotion rules in the C++ Standard are inherited from those in the C Standard, which were chosen not to describe how a language should most usefully behave, but rather to offer a behavioral description that was as consistent was practical with the ways many existing implementations had extended earlier dialects of C to add unsigned types.
Things get further complicated by an apparent desire to have the Standard specify behavioral aspects that were thought to be consistent among 100% of existing implementations, without regard for whether some other compatible behavior might be more broadly useful, while avoiding having the Standard impose any behavioral requirements on actions if on some plausible implementations it might be expensive to guarantee any behavior consistent with sequential program execution, but impossible to guarantee any behavior that would actually be useful.
I think it's pretty clear that the Committee wanted to unambiguously specify that long1 = uint1+1; uint2 = long1; and long1 = uint1+1; uint2 = long1; must set uint2 in a manner consistent with wraparound behavior in all cases, and did not want to forbid them from using wraparound behavior when setting long1. Although the Standard could have upheld the first requirement while implementations to promote to long on quiet-wraparound two's-complement platforms where the assignments to uint2 would yield results consistent with using wraparound behavior throughout, doing so would have meant including a rule specifically for quiet-wraparound two's-complement platforms, which is something C89 and--to an even greater extent C99--were exceptionally keen to avoid doing.