Search code examples
c++unsignedsignedstdsize-t

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.


Solution

  • 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.