Why does Qt use a signed integer type for its container classes?

The question is obvious.

I wonder why they even thought it would be convenient, since explicitly negative indexes are unsuitable for use in containers that will be used with them (see, for example, QList docs ).

I thought they want to resolve this for some crazy form of indexing, but does it seem unsupported?

It also generates a ton of (correct) compiler casting warnings and a comparison of signature types / unsigned (in MSVC).

For some reason, it just seems incompatible with STL in design ...

+10
c ++ qt qt4
source share
3 answers

Because, as a rule, you usually want to perform arithmetic on indexes, which means that you can create temporary, negative ones. This is clearly painful when the underlying index type is not specified.

The only suitable time for using unsigned numbers is module arithmetic. Using "unsgined" as a kind of contract specifier, the "number in the range [0 ..." is simply awkward and too crude to be useful.

Consider: what type should be used to represent the idea that a number should be a positive integer from 1 to 10? Why is 0 ... 2 ^ x a more special range?

+5
source share

Although I deeply sympathize with Chris' line of reasoning, I do not agree here (at least in part, I play the devil's advocate). There is nothing wrong with using unsigned types for sizes, and this can even be useful in some circumstances.

Chris justifies types with a size sign that they are naturally used as array indices, and you might want to do arithmetic with array indices, and this arithmetic can create temporary values ​​that are negative.

This subtle and unsigned arithmetic does not cause problems, provided that you will correctly interpret your values ​​when performing comparisons. Since the behavior of the overflow of unsigned integers is completely defined, temporary overflows in the negative range (or in huge positive numbers) do not introduce any errors if they are corrected before performing the comparison.

Sometimes overflow behavior is even desirable, since unsigned arithmetic overflow behavior makes certain range checks expressible as one comparison, which would otherwise require two comparisons. If I want to check if x in the range [a,b] and all unsigned values, I can simply do:

 if (x - a < b - a) { } 

This does not work with signed variables; such range checks are fairly common for array sizes and offsets.

I mentioned earlier that the benefit is that overflow arithmetic has certain results. If your index arithmetic overflows the signed type, the behavior is determined by the implementation; There is no way to make your program portable. Use an unsigned type and this problem will disappear. Admittedly, this applies only to huge offsets, but this applies to some applications.

In general, objections to unsigned types are often overestimated. The real problem is that most programmers do not really think about the exact semantics of the code they write, and for small integer values, signed types behave more in accordance with their intuition. However, data sizes are growing pretty fast. When we deal with buffers or databases, we often go beyond the "small" range, and signed overflows are much more problematic for proper handling than unsigned overflows. The solution is not to “not use unsigned types,” but to “think carefully about the code you write and make sure you understand it.”

+10
source share

This answer is more about supporting the main question: why did Qt, C # and others decide to use a signed type for things that, by definition, have no negative values?

With respect to signed / unsigned types, two things come to mind:

  1. Unsigned has no negative values ​​by definition

    So? Thus, if there is a domain value where any negative value does not make sense, then an unsigned type should be used. The size of the container is such a value. It makes no sense to pass -1 as an argument to resize() or operator[] (yes, I know, there are fancy Python behaviors and possibly other languages).

    From the point of view of the user:

    • What happens if I -1 as argument [] or resize()
    • Is it possible that size() < 0 ? What if there is?

    From the point of view of the developer:

    • What should the code do if the argument [] or resize() is <0 ? Throw an exception, do nothing, or allow the application to explode?

    They all disappear if an unsigned type is used (std :: size_t or another, allowing you to select as many elements as you need)

  2. A signed int restricts the number of values ​​to only 1<<31 == 2147483648

    If the user needs to highlight move items, then ... change the library? Split a dataset? This can be annoying if someone is working with large enough data sets.

  3. Negative indexing

    (disabled by one error)

    If you want to have indexing relative to the end [-1] then you can implement a very clear and error-proof interface. Signed values ​​are not required for this:

 // simple strictly typed wrapper for any value. Implement and name it as you would like to. using FromEnd = StrictTypeWrapper<std::size_t, struct FromEndTag>; //the use it like this auto value = container[FromEnd{1}]; 

So what is the meaning of the signed type for things that are not negative by definition?

0
source share

All Articles