7

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.

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
Arty
  • 14,883
  • 6
  • 36
  • 69
  • 2
    *But I need exactly same bit size as size_t but signed.* Use a `static_assert` for that: `static_assert(sizeof(std::size_t) == sizeof(std::ptrdiff_t), "error message");`. That's the best I know of. – NathanOliver Dec 29 '20 at 17:23
  • @NathanOliver So usually `ptrdiff_t` can be used as signed `size_t`, but you have to check to be sure? – Arty Dec 29 '20 at 17:27
  • Also [`std::streamsize`](https://en.cppreference.com/w/cpp/io/streamsize) "*used as a signed counterpart of `std::size_t`, similar to the POSIX type `ssize_t`*". – dxiv Dec 29 '20 at 17:29
  • From my experience, the two types have the same size. It's not guaranteed, but with the static assert you basically guarantee it. – NathanOliver Dec 29 '20 at 17:29
  • 6
    `ssize_t` isn't standard C++; it's POSIX. But really, if you need a signed version of `size_t`, then your way seems fine. I would guess there isn't a standard one because negative "sizes" don't really exist, so the standard prefers more meaningful names like `ptrdiff_t` when it needs stuff like that. – HTNW Dec 29 '20 at 17:33
  • 1
    There's [`std::ssize`](https://en.cppreference.com/w/cpp/iterator/size), but it may be larger than `size_t`. – user975989 Dec 29 '20 at 21:36
  • @user975989 I think you linked to the wrong one, that's the function page. In any case it may be worth keeping both types around. If you do get a bigger `ssize` then there will also be a corresponding `std::make_unsigned` of that. Oh on that note, does anyone know for sure if "the signed integral type corresponding to T" per `std::make_signed` is guaranteed identical in size, or if there's other logic involved? The page for `std::make_unsigned` is specific but only about enumerations. – John P Feb 03 '23 at 19:49

1 Answers1

6

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.

Artyer
  • 31,034
  • 3
  • 47
  • 75