I myself am convinced that in a project in which I work on whole signs, the best choice is in most cases, although the value contained inside can never be negative. (Simplification of reverse for loops, less chance of errors, etc., In particular, for integers that can only contain values between 0 and, say, 20).
Most of the places where this does not happen is a simple iteration of std :: vector, often it used to be an array and was later changed to std :: vector. So these loops usually look like this:
for (int i = 0; i < someVector.size(); ++i) { }
Because this pattern is used so often, the amount of compiler warning spam about this comparison between a signed and an unsigned type tends to obscure more useful warnings. Note that we definitely do not have vectors with more than INT_MAX elements and note that so far we have used two ways to fix the compiler warning:
for (unsigned i = 0; i < someVector.size(); ++i) { }
This usually works, but it can be interrupted if the loop contains any code like if (i-1> = 0) ... etc.
for (int i = 0; i < static_cast<int>(someVector.size()); ++i) { }
This change has no side effects, but makes the cycle much less readable. (And it prints more.)
So, I came up with the following idea:
template <typename T> struct vector : public std::vector<T> { typedef std::vector<T> base; int size() const { return base::size(); } int max_size() const { return base::max_size(); } int capacity() const { return base::capacity(); } vector() : base() {} vector(int n) : base(n) {} vector(int n, const T& t) : base(n, t) {} vector(const base& other) : base(other) {} }; template <typename Key, typename Data> struct map : public std::map<Key, Data> { typedef std::map<Key, Data> base; typedef typename base::key_compare key_compare; int size() const { return base::size(); } int max_size() const { return base::max_size(); } int erase(const Key& k) { return base::erase(k); } int count(const Key& k) { return base::count(k); } map() : base() {} map(const key_compare& comp) : base(comp) {} template <class InputIterator> map(InputIterator f, InputIterator l) : base(f, l) {} template <class InputIterator> map(InputIterator f, InputIterator l, const key_compare& comp) : base(f, l, comp) {} map(const base& other) : base(other) {} };
What you see is basically STL classes with methods that return the size_type parameter overridden to return only "int". Constructors are necessary because they are not inherited.
What would you think of it as a developer if you saw such a solution in the existing code base?
Could you think: “Yes, they redefine STL, what a huge WTF!”, Or do you think that this is a simple solution to prevent errors and increase readability. Or maybe you would prefer that we spend (half) a day or so changing all these loops to use std :: vector <> :: iterator?
(In particular, if this solution was combined with the prohibition of using unsigned types for anything other than raw data (for example, unsigned char) and bit masks.)