Arty
Arty

Reputation: 16747

Signed variant of size_t in standard C++ library

Is there a signed variant of size_t in standard C++? Meaning exactly same bit size as size_t but signed.

Of course I can do:

#include <type_traits>
using signed_size_t = std::make_signed_t<std::size_t>;

but maybe there is already similar definition in standard library, not to invent extra type name?

I know there are ssize_t and ptrdiff_t, both signed. But according to theirs description it seems that they can both be of different bit size than size_t. But I need exactly same bit size as size_t but signed.

Upvotes: 8

Views: 2707

Answers (1)

Artyer
Artyer

Reputation: 40811

There is one place where the "signed version of std::size_t" (and also the unsigned version of std::ptrdiff_t) comes up in the standard: The printf format specifier %zu is for std::size_t objects. %zd is for objects of, as the C standard which the C++ standard refers to says, "the corresponding signed integer type [of std::size_t]"

std::printf("%zu %zd %td %tu",
    std::size_t{0}, std::make_signed_t<std::size_t>{0},
    std::ptrdiff_t{0}, std::make_unsigned_t<std::ptrdiff_t>{0}
);

And since there is no type specifically named for %zd and %tu, I'm inclined to believe there is no standard name like you want (other than as std::make_signed_t<std::size_t>).


As an aside, there is not much reason to want a signed variant of std::size_t: std::size_t is for the size of an object, and an object's size isn't signed.

ssize_t is only guaranteed to hold either -1 or a non-negative value. It's guaranteed range is [-1, SSIZE_MAX] (And is a POSIX-specific type, not a standard C++ type). This is because it's used for "an unsigned value or -1 on error".

The C++ standard library just uses std::size_t for this, with std::size_t(-1) == SIZE_MAX instead to indicate the error/special value (See: std::basic_string<...>::npos, std::dynamic_extent), so you can just use std::size_t instead of ssize_t if you wanted an error value (or maybe std::optional<std::size_t>)


If you instead wanted "something to represent a size but is signed", std::ssize(c) ("signed size") returns std::common_type_t<std::ptrdiff_t, std::make_signed_t<decltype(c.size())>>. For array types, std::ssize returns std::ptrdiff_t. So probably use std::ptrdiff_t for this purpose.


If you wanted "the type used to represent the distance between two iterators" (including pointers), std::ptrdiff_t was made for this. This mostly coincides with the concept of signed sizes, and std::iterator_traits<...>::difference_type is usually std::ptrdiff_t.


These does not mean that sizeof(std::ptrdiff_t) == sizeof(std::size_t). The standard does not define any relationship between them. Both of sizeof(std::ptrdiff_t) < sizeof(std::size_t) and sizeof(std::ptrdiff_t) > sizeof(std::size_t) seem theoretically possible, but I have not found any systems where this is the case. So a simple assertion should work on all platforms and allow you to just use std::ptrdiff_t:

static_assert(
    sizeof(std::size_t) == sizeof(std::ptrdiff_t) &&
    static_cast<std::size_t>(std::numeric_limits<std::ptrdiff_t>::max()) == std::numeric_limits<std::size_t>::max() / 2u,
    "ptrdiff_t and size_t are not compatible"
);

(There are many systems where std::size_t is unsigned int and std::ptrdiff_t is signed long but sizeof(int) == sizeof(long), so we have to check the ranges of the types rather than std::is_same_v<std::ptrdiff_t, std::make_signed_t<std::size_t>>)

Or just use std::make_signed_t<std::size_t> like you already have.

Upvotes: 8

Related Questions