Hello and welcome to C++ Weekly. I'm your host, Jason Turner, available for code reviews and
on-site training. Now in this episode I want to discuss a new feature from C++20, but I also
want to make a point about our way that we write code, warnings that we enable, this kind of thing;
and I kind of hope that you take this one seriously because this is, you know, perhaps
a bigger deal for code correctness than you might assume. So let's go ahead and dig into this.
All right, now let's say we've got our simple example 'main' here, and we've got
something that gets a value, and we've got another function that gets another value,
and we just want to return whether or not get_value1 is less than get_value 2. Now note
I am getting this warning - "warning: comparison of integers of different signs." I've got int and
unsigned int, and this comes with -Wsign-compare. Now at the moment I have -W everything turned on.
Let's just go ahead and remove a couple of these warnings. I am using clang trunk in C++20 mode,
so I've got -Wall, -Wextra. Let's see, okay, so I need -Wextra to get this enabled. Now I've got
-Wextra and I'm getting this warning. Now your inclination might be one of two things, and I
know this because I've worked with C++ developers for a long time and have been one myself. Your
first inclination might be to say, "Oh we're just getting so many of these sign comparison warnings
I'm just going to disable those." That is, I am 100% convinced, something that would happen here.
Your other inclination might be to do something like a static_cast
and um, so, we just want to make the other one an int and then our problems all go away. I also
want you to reconsider that option, and I will make this example slightly better. This one I'm
going to return -1, and in this one I am going to return the value 1; and I am saying "is value1
less than value2," and value1 is returning -1, value2 is returning 1. The compiler optimizes this
all away at compile time and tells us "false." It is returning 0. It is saying -1 is not less
than 1. Okay, so let's just go ahead and try a couple of these static_casts and see where we get.
Okay, if I do a static_cast to int on the right hand side, then I get that
-1 is in fact less than 1. That's good, but if I do it the other way around -
I cast to an unsigned int on the left-hand side, then I'm going to get "false" here. So
this is where things go wrong. So let's do a quick examination as to what's happening.
Now in case you didn't know the value -1, and I'm going to do this in eight bit values
just for simplicity, value -1 in two's complement representation is going to be
equivalent to all ones, and the value 1 (now this is two's complement signed integer math which
all modern computers use) the value one will be represented by just the final bit being set to 1.
So if it treats these as both unsigned values then, -1 becomes equivalent to max int, or in
the case of my 8-bit values, 255; and then this is just 1. So it says, "Okay, well, is 255 less
than 1? No, false," and it returns false here; and that's equivalent to what it is actually doing and
what we can see from the compiler on our output. Doing a static_cast is a little bit sketchy.
It seems to work in some situations. It's not going to work in every situation,
so our standard library for C++20 actually gave us a helper for this. So we have now in the utility
header in C++20 a set of safe integer comparisons: cmp_equal, not_equal, less, greater, less_equal or
greater_equal, and we can dig into these and fortunately, we can see since they were added
in C++20 they are all constexpr capable. So let's go ahead and do cmp_less from the utility header.
Okay, is -1 less than 1? Yes. Now the compiler can see this all the way through at compile time.
Now I'm going to go ahead and remove the body of these things
again, and so we can kind of see what the compiler is generating;
and you can actually see it well, it generated a bit more, but it knows how to do the proper
tests and comparisons here and it's hopefully something that's quite efficient for your platform
for these comparisons. So absolutely be aware that these things exist, and make sure that you're
using them instead of disabling your compiler warnings. Now it is interesting to note that these
are templated on both the left-hand side and the right-hand side. You can give it any two signed or
unsigned types on either side. Unlike built-in comparison operators, negative signed integers
always compare less than and not equal to unsigned integers. The comparison is safe against lossy
integer conversions. CppReference.com is going to give you a bunch of examples here as always, plus
possible implementations of each of these things that if you need to,
you could borrow for your existing code base while you wait for your company to move to C++20.
So thanks for watching this episode of C++ Weekly, and be sure to subscribe!