C++ For-Loop - Size_Type VS. Size_T

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 is unsigned integral, while int is not necessarily unsigned. :-)
  • 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.


  1. 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 the std namespace is a specialization of an existing class for a user-defined type, so for some T, vector<T> could use a completely different container than for most other types. This is typical for vector<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 by vector, 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 values a 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



Leave a reply



Submit