Is there still a reason to use `int` in C++ code? [duplicate]
There was a discussion on the C++ Core Guidelines what to use:
https://github.com/isocpp/CppCoreGuidelines/pull/1115
Herb Sutter wrote that gsl::index
will be added (in the future maybe std::index
), which will be defined as ptrdiff_t
.
hsutter commented on 26 Dec 2017 •
(Thanks to many WG21 experts for their comments and feedback into this note.)
Add the following typedef to GSL
namespace gsl { using index = ptrdiff_t; }
and recommend
gsl::index
for all container indexes/subscripts/sizes.Rationale
The Guidelines recommend using a signed type for subscripts/indices. See ES.100 through ES.107. C++ already uses signed integers for array subscripts.
We want to be able to teach people to write "new clean modern code" that is simple, natural, warning-free at high warning levels, and doesn’t make us write a "pitfall" footnote about simple code.
If we don’t have a short adoptable word like
index
that is competitive withint
andauto
, people will still useint
andauto
and get their bugs. For example, they will writefor(int i=0; i<v.size(); ++i)
orfor(auto i=0; i<v.size(); ++i)
which have 32-bit size bugs on widely used platforms, andfor(auto i=v.size()-1; i>=0; ++i)
which just doesn't work. I don’t think we can teachfor(ptrdiff_t i = ...
with a straight face, or that people would accept it.If we had a saturating arithmetic type, we might use that. Otherwise, the best option is
ptrdiff_t
which has nearly all the advantages of a saturating arithmetic unsigned type, except only thatptrdiff_t
still makes the pervasive loop stylefor(ptrdiff_t i=0; i<v.size(); ++i)
emit signed/unsigned mismatches oni<v.size()
(and similarly fori!=v.size()
) for today's STL containers. (If a future STL changes its size_type to be signed, even this last drawback goes away.)However, it would be hopeless (and embarrassing) to try to teach people to routinely write
for (ptrdiff_t i = ... ; ... ; ...)
. (Even the Guidelines currently use it in only one place, and that's a "bad" example that is unrelated to indexing`.)Therefore we should provide
gsl::index
(which can later be proposed for consideration asstd::index
) as a typedef forptrdiff_t
, so we can hopefully (and not embarrassingly) teach people to routinely write for(index i = ... ; ... ; ...)
.Why not just tell people to write
ptrdiff_t
? Because we believe it would be embarrassing to tell people that's what you have to do in C++, and even if we did people won't do it. Writingptrdiff_t
is too ugly and unadoptable compared toauto
andint
. The point of adding the nameindex
is to make it as easy and attractive as possible to use a correctly sized signed type.
Edit: More rationale from Herb Sutter
Is
ptrdiff_t
big enough? Yes. Standard containers are already required to have no more elements than can be represented byptrdiff_t
, because subtracting two iterators must fit in a difference_type.But is
ptrdiff_t
really big enough, if I have a built-in array ofchar
orbyte
that is bigger than half the size of the memory address space and so has more elements than can be represented in aptrdiff_t
? Yes. C++ already uses signed integers for array subscripts. So useindex
as the default option for the vast majority of uses including all built-in arrays. (If you do encounter the extremely rare case of an array, or array-like type, that is bigger than half the address space and whose elements aresizeof(1)
, and you're careful about avoiding truncation issues, go ahead and use asize_t
for indexes into that very special container only. Such beasts are very rare in practice, and when they do arise often won't be indexed directly by user code. For example, they typically arise in a memory manager that takes over system allocation and parcels out individual smaller allocations that its users use, or in an MPEG or similar which provides its own interface; in both cases thesize_t
should only be needed internally within the memory manager or the MPEG class implementation.)
I come at this from the perspective of an old timer (pre C++)... It was understood back in the day that int
was the native word of the platform and was likely to give the best performance.
If you needed something bigger, then you'd use it and pay the price in performance. If you needed something smaller (limited memory, or specific need for a fixed size), same thing.. otherwise use int
. And yeah, if your value was in the range where int on one target platform could accommodate it and int on another target platform could not.. then we had our compile time size specific defines (prior to them becoming standardized we made our own).
But now, present day, processors and compilers are much more sophisticated and these rules don't apply so easily. It is also harder to predict what the performance impact of your choice will be on some unknown future platform or compiler ... How do we really know that uint64_t for example will perform better or worse than uint32_t on any particular future target? Unless you're a processor/compiler guru, you don't...
So... maybe it's old fashioned, but unless I am writing code for a constrained environment like Arduino, etc. I still use int
for general purpose values that I know will be within int
size on all reasonable targets for the application I am writing. And the compiler takes it from there... These days that generally means 32 bits signed. Even if one assumes that 16 bits is the minimum integer size, it covers most use cases.. and the use cases for numbers larger than that are easily identified and handled with appropriate types.