C++ for-loop - size_type vs. size_t
The C++ Standard says,
size_type | unsigned integral type | a type that can represent the size of the largest object in the
allocation model
Then it adds,
Implementations of containers
described in this International
Standard are permitted to assume that
their Allocator template parameter
meets the following two additional
requirements beyond those in Table 32.
- The typedef members pointer, const_pointer, size_type, and
difference_type are
required to be T*,T const*, size_t, and ptrdiff_t, respectively
So most likely, size_type
is a typedef of size_t
.
And the Standard really defines it as,
template <class T>
class allocator
{
public:
typedef size_t size_type;
//.......
};
So the most important points to be noted are :
size_type
isunsigned
integral, whileint
is not necessarilyunsigned
. :-)- it can represent the largest index, because it's unsigned.
When should I use vector int ::size_type instead of size_t?
The primary time to use size_type
is in a template. Although std::vector<T>::size_type
is usually size_t
, some_other_container<T>::size_type
might be some other type instead1. One of the few things a user is allowed to add to the std
namespace is a specialization of an existing template for some user defined type. Therefore, std::vector<T>::size_type
for some oddball T
could actually be some type other than size_t
, even though the base template defined in the standard library probably always uses size_t
.
Therefore, if you want to use the correct type for a specific container inside a template that works with that container, you want to use container::size_type
instead of just assuming size_t
.
Note, however, that generic code should rarely work directly with a container. Instead, it should typically work with iterators, so instead of container<T>::size_type
, it would typically use something like std::iterator_traits<WhateverIterator>::difference_type
instead.
- And for some specific
T
,vector<T>::size_type
might be a different type as well--one of the few things you're allowed to put into thestd
namespace is a specialization of an existing class for a user-defined type, so for someT
,vector<T>
could use a completely different container than for most other types. This is typical forvector<bool>
, but possible for other types as well.
When to use std::size_t?
A good rule of thumb is for anything that you need to compare in the loop condition against something that is naturally a std::size_t
itself.
std::size_t
is the type of any sizeof
expression and as is guaranteed to be able to express the maximum size of any object (including any array) in C++. By extension it is also guaranteed to be big enough for any array index so it is a natural type for a loop by index over an array.
If you are just counting up to a number then it may be more natural to use either the type of the variable that holds that number or an int
or unsigned int
(if large enough) as these should be a natural size for the machine.
size_t' vs 'container::size_type'
The standard containers define size_type
as a typedef to Allocator::size_type
(Allocator is a template parameter), which for std::allocator<T>::size_type
is typically defined to be size_t
(or a compatible type). So for the standard case, they are the same.
However, if you use a custom allocator a different underlying type could be used. So container::size_type
is preferable for maximum generality.
difference between size_type and int
vector<double>::size_type
is guaranteed to cover the full range of possible values of the size of a vector<double>
. An int
is not.
Note that vector<double>::size_type
is usually the same as std::size_t
, so in general it would be OK to use the latter. However, custom allocators can result in the a vector with a size_type
different to std::size_t
.
std::size_t vs. size_type as parameters and function return types
but of course there's the off-chance that the size of the vector isn't of type std::size_t
Such off-chance doesn't practically exist in this case because std::vector<MyClass*>::size_type
is (indirectly guaranteed and required to be) of type std::size_t
. Using std::size_t
is fine in this case, and it doesn't leak unnecessary implementation details.
In the case of standard containers, Container::size_type
is defined based directly on on what allocator is being used. Thus, using size_type
is typically only necessary when the allocator type - or the container type itself - is templated. In the allocator case, you can use allocator traits instead of the container member type which allows you to keep the container type hidden. If the container type itself is templated, then there is no point in hiding it since only someone who knows the container could have instantiated the template in the first place.
Furthermore, you can hide - or rather obfuscate (in a positive, encapsulating way) - the function declaration by creating a type alias member, just like std::vector
has a type alias member based on its allocator.
Example:
template<class Alloc>
class Foo
{
// could be hidden with PIMPL if desired
std::vector<MyClass*, Alloc> m_myVector;
public:
// Since C++11
using size_type = typename std::allocator_traits<Alloc>::size_type;
// Prior to C++11
typedef typename Alloc::size_type size_type;
size_type size();
};
std::size_t or std::vector Foo ::size_type?
It is not necessarily true that std::vector<Foo>::size_type
is the same as std::size_t
. This is true even for C++11.
But personally I use std::size_t
for a std::vector
index irrespective of the type.
You could always use a static assertion if you're feeling particularly diligent. Obviously static_assert
is a later addition beyond what's in C++98, but in that standard you could use something like
static char wrong_size_1[1 + sizeof(std::size_t) - sizeof(std::vector<Foo>::size_type)];
static char wrong_size_2[1 - sizeof(std::size_t) + sizeof(std::vector<Foo>::size_type)];
which would induce compile time failures if type types are not the same size.
Stroustrup and overflowing size_type in a loop
Well, yes, the paragraph you quoted does seem to indirecly imply or hint that size_type
can be problematic. But I don't think it was the author's intent.
Note that the previous paragraph says (re: Second Edition)
So, technically, most of the loops in this book have been sloppy [...]. To avoid this problem we can use the
size_type
provided byvector
, iterators, or a range-for
-statement:
In this paragraph size_type
is presented a solution for potentially problematic int
loops used in the previous sections of the book. It is mentioned as a safe alternative together with iterator or range-for version of the loop.
The potential overflow (or insufficient range) problem does exist when someone tries to use std::size_t
to count or index elements of a non-array-based container, like std::list
, std::deque
, std::map
etc,, instead of using container's own size_type
. But it is a slightly different story, even if it is related.
Is `size_t` always an alias for `vector int ::size_type` or any other container type?
std::vector
guarantees that pointers are valid iterators for its entire sequence, because vector::data
returns "A pointer such that [data(), data() + size())
is a valid range." That's pointer addition, which is defined over std::ptrdiff_t
, which is the signed version of std::size_t
.
Also, [iterator.requirements.general]/6 applies:
… for integral values
n
and dereferenceable iterator valuesa
and(a + n)
,*(a + n)
is equivalent to*(addressof(*a) + n)
…
It's possible for vector::size_type
to be narrower than std::size_t
, for example 32 bits on a 64-bit system. But it's not something I'd worry about.
Related Topics
Writing Utf16 to File in Binary Mode
Why Is Protected Constructor Raising an Error This This Code
Intrinsics for Cpuid Like Informations
Does Static Constexpr Variable Inside a Function Make Sense
Common Array Length MACro for C
How Are C++ Array Members Handled in Copy Control Functions
How to Create a New Operator in C++ and How
Is a C++ Preprocessor Identical to a C Preprocessor
How to Write to a Memory Buffer With a File*
Default Class Inheritance Access
Declaring Multiple Object Pointers on One Line Causes Compiler Error
Dependency Injection with Unique_Ptr to Mock
What Does {0} Mean When Initializing an Object
Does the C++ Volatile Keyword Introduce a Memory Fence
Benefits of Ternary Operator VS. If Statement
Why Cannot a Non-Member Function Be Used for Overloading the Assignment Operator