Decreasing Signed and Unsigned Mismatches with std::ssize()

[ad_1]

On this article, I’ll present one other approach that avoids mixing signed and unsigned varieties.

In my article Integer Conversions and Secure Comparisons in C++20 we realized about cmp_* integer comparability features that permit us to match numerous forms of integer varieties. The features are secure as a result of they assist with mixing signed and unsigned varieties. In C++20, there’s one other useful function: the non-member std::ssize() perform that returns a signed variety of parts in a container.

Let’s see how one can use it.

Use circumstances

 

C++20 presents some ways to keep away from working with uncooked array/container indices:

  • Algorithms (they’ll keep away from uncooked loops),
  • Ranges and views (for instance, views::reverse),
  • Iterators,
  • Vary based mostly for loop with an initializer (for instance, to replace the index rely).

Alternatively, chances are you’ll someday want to jot down/learn or repair a loop like:

void printReverse(const std::vector<int>& v) {
    for (auto i = v.measurement()-1; i >= 0; --i)
        std::cout << i << ": " << v[i] << 'n';
}

int principal() {
    std::vector v { 1, 2, 3, 4, 5 };
    printReverse(v);
}

Are you able to see the error?

You’ll be able to even attempt operating that on Compiler Explorer

I get the next output:

4: 5
3: 4
2: 3
1: 2
0: 1
18446744073709551615: 0
18446744073709551614: 33
18446744073709551613: 0
...

Clearly, the code is improper because it loops infinitely!

A developer who wrote the code labored in a signed “arithmetic” and assumed auto i = ... would give him a signed sort. However since v.measurement() returns size_t then the i index might be additionally size_t.

In C++20 we’ve now an possibility to make use of ssize() – a non member perform that returns a signed worth:

void printReverseSigned(const std::vector<int>& v) {
    for (auto i = std::ssize(v)-1; i >= 0; --i)
        std::cout << i << ": " << v[i] << 'n';
}

Now, we keep within the “signed” camp and might use our common arithmetic.

As a aspect be aware, you may keep “unsigned”, however it’s a must to use the modulo 2 arithmetic:

void printReverseUnsigned(const std::vector<int>& v) {
    for (size_t i = v.measurement()-1; i < v.measurement(); --i)
        std::cout << i << ": " << v[i] << 'n';
}

Be sure to use compiler choices like -Wall, -Wsign-conversion, -Wsign-comparison (GCC/Clang), or /Wall on MSVC to catch all errors and warnings associated to mixing indicators.

I additionally discovered this code in Firefox (utilizing this code search):

// actcd19/principal/f/firefox-esr/firefox-esr_60.5.1esr-1/gfx/angle/checkout/src/libANGLE/Program.cpp:1570:

const LinkedUniform &Program::getUniformByLocation(GLint location) const
{
    ASSERT(location >= 0 && static_cast<size_t>(location) <           
           mState.mUniformLocations.measurement());
    return mState.mUniforms[mState.getUniformIndexFromLocation(location)];
}

For the reason that location is signed, then we might use ssize() and keep away from static casting:

ASSERT(location >= 0 && location < std::ssize(mState.mUniformLocations);

The proposal

 

The proposal for the function, that obtained accepted in C++20, is P1227R2, written by Jorg Brown. The paper additionally exhibits the next instance:

template <typename T>
bool has_repeated_values(const T& container) {
  for (int i = 0; i < container.measurement() - 1; ++i) {
    if (container[i] == container[i + 1]) return true;
  }
  return false;
}

In that case, we’ll get an error when the container is empty, nevertheless it can be mounted with ssize() or correctly fixing the logic.

The necessity for signed measurement got here from the design of std::span. Initially, it had signed indices and measurement, however to adapt to the STL guidelines, it was modified to unsigned varieties.

ssize() joined different non-member features out there since C++11 and C++17:

  • measurement() since C++17
  • start()/finish() and cbegin()/cend(), since C++11
  • empty() since C++17

Because of ADL (Argument Dependent Lookup) you can too use these features with out writing std:::

std::vector vec = ...
if (!empty(vec)) {
    for (int i = 1; i < ssize(vec); ...) {
        ...
    }
}

Implementation

 

If we glance into MSVC code, we will see the implementation for ssize():

template <class _Container>
_NODISCARD constexpr auto ssize(const _Container& _Cont)
    -> common_type_t<ptrdiff_t, make_signed_t<decltype(_Cont.measurement())>> {
    utilizing _Common = common_type_t<ptrdiff_t, 
                    make_signed_t<decltype(_Cont.measurement())>>;
    return static_cast<_Common>(_Cont.measurement());
}

template <class _Ty, ptrdiff_t _Size>
_NODISCARD constexpr ptrdiff_t ssize(const _Ty (&)[_Size]) noexcept {
    return _Size;
}

In brief the features return a appropriate signed sort, normally ptrdiff_t. ptrdiff_t is used for pointer arithmetic and it’s signed.

Each size_t and ptrdiff_t are normally 64-bit varieties on x86-64 and 32 bits on x86, so it’s a uncommon likelihood your containers will maintain extra parts than ptrdiff_t can signify. On x86 programs, you will have 31 bits for the dimensions, whereas on 64-bit programs, you will have 63 bits (to signify a optimistic signed worth). It’s extremely unlikely that you just’ll attain that measurement and received’t use full reminiscence by that point.

Controversy

 

Through the analysis, I additionally discovered an argument round having unsigned as a return sort for measurement(). In some circumstances, unsigned could be thought-about as one other “improper default” for C++.
The reason being associated to the truth that pointer arithmetic would possibly yield adverse signed values, whereas we frequently have sizes which might be unsigned. Mixing these varieties can result in numerous hard-to-find runtime errors.

Some folks desire to be within the “unsigned” camp, whereas others are within the “signed” one. In actual life, there are possibilities that it’s essential go exterior your camp and use the alternative signal for a variable. In that case, be sure you use applicable arithmetic features to stop errors.

Some notes:

Abstract

 

This quick article confirmed one other choice to take care of sizes for numerous containers and arrays. If in case you have a signed sort and need to evaluate it with measurement, then use ssize(), which returns ptrdiff_t, a signed sort.

Concerning computations, observe the Arithmetic part within the C++ Core Guideline. Particularly ES.100: Don’t combine signed and unsigned arithmetic and ES.102: Use signed varieties for arithmetic.

Again to you

Are you within the “signed” or “unsigned” camp? Have you ever obtained any points with measurement() being unsigned? Share your suggestions within the feedback beneath.

[ad_2]

Leave a Comment

Your email address will not be published. Required fields are marked *