28

I'm writing C++ code in an environment in which I don't have access to the C++ standard library, specifically not to std::numeric_limits. Suppose I want to implement

template <typename T> constexpr T all_ones( /* ... */ )

Focusing on unsigned integral types, what do I put there? Specifically, is static_cast<T>(-1) good enough? (Other types I could treat as an array of unsigned chars based on their size I guess.)

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 4
    I think that the language standard does not dictate 2's complement, so perhaps use `~0` instead of `-1`... Oh, there's already an answer which states exactly (or more or less) what I thought... – barak manos Apr 17 '16 at 12:10
  • So you don't have access to ``, either? Because otherwise, you could quite easily implement something like `std::numeric_limits` yourself. – Christian Hackl Apr 17 '16 at 15:32
  • @ChristianHackl: Not sure about that. But, with limits.h, I could have explicit specializations for all numeric types at least. – einpoklum Apr 17 '16 at 16:04
  • 1
    Possible duplicate of [Is it safe to use -1 to set all bits to true?](http://stackoverflow.com/questions/809227/is-it-safe-to-use-1-to-set-all-bits-to-true) – David Stone Apr 17 '16 at 16:20
  • @DavidStone: In this question we have: 1. template polymorphism and 2. non-integral types as a secondary part of the question, so I'd say not quite a dupe. – einpoklum Apr 17 '16 at 16:47

5 Answers5

32

Use the bitwise NOT operator ~ on 0.

T allOnes = ~(T)0;

A static_cast<T>(-1) assumes two's complement, which is not portable. If you are only concerned about unsigned types, hvd's answer is the way to go.

Working example: https://ideone.com/iV28u0

Community
  • 1
  • 1
Leandros
  • 16,805
  • 9
  • 69
  • 108
  • 1
    It is correct that two's complement is not used everywhere, and code relying on it is not portable, but it *is* used in the overwhelming majority of systems. – owacoder Apr 17 '16 at 11:32
  • 10
    If `T` is `short`, then `~(short)0` first promotes `(short)0` back to int. You then end up with a value that could be outside `short`'s range, resulting in a conversion with an implementation-defined result that need not be all bit ones. (This isn't possible with two's complement, but your answer specifically tries to address portability to other systems.) –  Apr 17 '16 at 11:36
  • @hvd Why would the short be promoted? Is the global ~ operator not guaranteed to have a short implementation? – Andreas Apr 17 '16 at 12:30
  • 4
    @Andreas Good question. It's because of integral promotion, which is inherited from C. C was designed by dmr to be easy for compiler writers, therefore everything which can be represented as an `int`, is promoted to an int, if it can't be represented as a `signed int`, it's promoted to an `unsigned int`. – Leandros Apr 17 '16 at 12:36
  • 4
    The other good point about this approach is that it is **expressive**. You want all ones, which is the opposite of all zeroes. People reading it know what it does. The "minus-one-twos-complement" approach isn't as expressive and relies on implementation knowledge. – Kate Gregory Apr 17 '16 at 14:40
  • @KateGregory Where do you draw the line though? Do you think it is only confusing to use numbers to represent bit patterns when negative numbers are used, or would you also avoid constants in some cases with positive numbers, for example when the concrete bits depend on the width of the type? For me, I think it would depend on the reason for wanting a value with all bits one in the first place. In some cases, I might think `-1` is unclear, but in others, I might not. –  Apr 18 '16 at 07:17
  • @KateGregory: probably the reason this question comes up over and over again, is that in both C and C++ there is a tension between "expressive" and "portable". This is expressive, but as hvd points out the standard surprisingly doesn't guarantee that it works for unsigned types smaller than `int`, because they get converted to `int` before applying `~`. Then the result is converted to `T`, but the result of that conversion depends on the representation of `int`, so you only get all bits 1 in two's complement. Of course, approximately nobody *needs* portability to non-2's-complement systems. – Steve Jessop Apr 18 '16 at 09:20
  • In fact, I think I'd say that it is not possible in C or C++ to write "expressive" arithmetic expressions involving unsigned types smaller than `int`, because the standard arithmetic conversions are doing too much implicit work. Anything you do is obscure per the standard, although in a "nice" implementation most expressions will actually do what mathematics would expect them to do. In C you just wouldn't attempt maths with small types if you need complete portability, but in C++ you trick yourself into doing so via templates. – Steve Jessop Apr 18 '16 at 09:24
  • There is no truly portable way, which works for both `signed` and `unsigned` types. `~0` on unsigned relies on two's complement, and `-1` on signed relies on two's complement. – Leandros Apr 18 '16 at 10:18
22

Focusing on unsigned integral types, what do I put there? Specifically, is static_cast(-1) good enough

If you're only concerned about unsigned types, yes, converting -1 is correct for all standard C++ implementations. Operations on unsigned types, including conversions of signed types to unsigned types, are guaranteed to work modulo (max+1).

  • "all standard C++ implementations" - talk about a confusing label. is this specified by the "standard", or is it "implementation"-defined like the other answer claims? – underscore_d Apr 17 '16 at 11:53
  • 2
    @underscore_d It's indeed well defined by the C++ standard. unsigned types are required to perfectly overflow. It does **not** work for signed types, signed overflow is undefined. My answer is implementation defined or either, but one can assume that any sane implementation does what you expect it to do. – Leandros Apr 17 '16 at 11:55
  • @underscore_d My solution does not rely on it, neither does hvd's solution. – Leandros Apr 17 '16 at 12:00
  • @Leandros Yeah, nvm, the modulo is the key here. – underscore_d Apr 17 '16 at 12:02
  • @underscore_d Exactly. ;) – Leandros Apr 17 '16 at 12:02
  • If I understand the implications of this correctly, casting between same sized signed and unsigned types will give different results between reinterpret-casting and static-casting on machines which don't use two's complement. Is that correct? – CodesInChaos Apr 18 '16 at 09:08
  • @CodesInChaos That's correct. Given `int i = -1;`, the comparison `reinterpret_cast(i) == static_cast(i)` is allowed by the aliasing rule, but it is not guaranteed to evaluate as `true`. The behaviour is not necessarily defined, even: it's allowed that `UINT_MAX == INT_MAX`, where the sign bit in `i` being set would produce what C would call a trap representation when reinterpreted as unsigned. –  Apr 18 '16 at 09:15
  • doesn't that require that the max is one less than a power of two? And the standard only defines a minimum value for the max. – Tim Apr 18 '16 at 11:02
  • @Tim Yes, it requires that the max is one less than a power of two, but that is guaranteed by the standard. For unsigned types it's required explicitly ([basic.fundamental]p4). For completeness (not relevant to my answer), for signed types it's required implicitly: it's not specifically mentioned, but there's no way to meet the complete standard's requirements otherwise. –  Apr 18 '16 at 12:51
8

This disarmingly direct way.

T allOnes;
memset(&allOnes, ~0, sizeof(T));
OmnipotentEntity
  • 16,531
  • 6
  • 62
  • 96
  • @Leandros and Omni, how/when is aliasing an issue? – Andreas Apr 17 '16 at 12:12
  • @Andreas Thinking about it, never. – Leandros Apr 17 '16 at 12:17
  • 13
    This is only valid for 8-bit bytes. – ach Apr 17 '16 at 12:17
  • 11
    I want to initialize a constexpr expression, not set a value at run-time. – einpoklum Apr 17 '16 at 12:18
  • Why would this break strict aliasing? – Emil Laine Apr 17 '16 at 12:51
  • @zenith, I couldn't prove to myself that it wouldn't. And it was too late at night for me to justify checking before posting, so I put in that disclaimer. Looking back it seems it doesn't. However, this is not really a valid answer for the reason that einpoklum posted, (I missed the `constexpr` in the question.) – OmnipotentEntity Apr 17 '16 at 18:36
  • @AndreyChernyakhovskiy: Isn't CHAR_BIT required to be 8? – einpoklum Apr 21 '16 at 12:45
  • 1
    @einpoklum, It is required to be *at least* 8. Although 8-bit byte has become dominant in the industry, other sizes are still met; at least, non-8-bit bytes are more probable to meet that non-2's-complement negatives. This issue is easy to address, though, by just using `~0` instead of `0xFF`. – ach Apr 21 '16 at 14:25
7

Focusing on unsigned integral types, what do I put there? Specifically, is static_cast(-1) good enough

Yes, it is good enough.

But I prefer a hex value because my background is embedded systems, and I have always had to know the sizeof(T).

Even in desktop systems, we know the sizes of the following T:

uint8_t  allones8  = 0xff;
uint16_t allones16 = 0xffff;
uint32_t allones32 = 0xffffffff;
uint64_t allones64 = 0xffffffffffffffff;
2785528
  • 5,438
  • 2
  • 18
  • 20
4

Another way is

static_cast<T>(-1ull)

which would be more correct and works in any signed integer format, regardless of 1's complement, 2's complement or sign-magnitude. You can also use static_cast<T>(-UINTMAX_C(1))

Because unary minus of an unsigned value is defined as

The negative of an unsigned quantity is computed by subtracting its value from 2^n, where n is the number of bits in the promoted operand."

Therefore -1u will always return an all-one-bits data in unsigned int. ll suffix is to make it work for any types narrower than unsigned long long. There's no extended integer types (yet) in C++ so this should be fine

However a solution that expresses the intention clearer would be

static_cast<T>(~0ull)
phuclv
  • 37,963
  • 15
  • 156
  • 475