10

If you need a counting variable, surely there must be an upper and a lower limit that your integer must support. So why wouldn't you specify those limits by choosing an appropriate (u)int_fastxx_t data type?

snips-n-snails
  • 637
  • 5
  • 22
  • 1
    "`int` is shorter to type" is probably just about the best reason. `int` is effectively semantically `int_fast16_t` (i.e., a fast type that's guaranteed to be at least 16 bits), though practically, on x86_64 Linux, int is `32` bits, and all fast types are except (u)int_fast8_t are `64`. Regardless of what you use, a compiler will likely put an often used counter in a register, so AFAIK, you might as well use something that has the width of your registers as that's what'll get used anyway. – Petr Skocik Apr 16 '17 at 20:18
  • 14
    Because you mostly don't need to concern yourself with such low-level details when writing C++ code. `int` is a perfectly adequate abstraction for *"general-purpose integer number with reasonable performance"*. You abandon the abstraction only when you have to, not the other way round. – Christian Hackl Apr 16 '17 at 20:21
  • 1
    @ChristianHackl But knowing when to abandon the abstraction requires you to know the size of `int` on every platform that your code will run on. – snips-n-snails Apr 16 '17 at 22:53
  • 1
    @traal - Buy you don't know the size of `int_fast16_t` either... – Bo Persson Apr 18 '17 at 09:06

2 Answers2

5

The simplest reason is that people are more used to int than the additional types introduced in C++11, and that it's the language's "default" integral type (so much as C++ has one); the standard specifies, in [basic.fundamental/2] that:

Plain ints have the natural size suggested by the architecture of the execution environment46; the other signed integer types are provided to meet special needs.

46) that is, large enough to contain any value in the range of INT_MIN and INT_MAX, as defined in the header <climits>.

Thus, whenever a generic integer is needed, which isn't required to have a specific range or size, programmers tend to just use int. While using other types can communicate intent more clearly (for example, using int8_t indicates that the value should never exceed 127), using int also communicates that these details aren't crucial to the task at hand, while simultaneously providing a little leeway to catch values that exceed your required range (if a system handles signed overflow with modulo arithmetic, for example, an int8_t would treat 313 as 57, making the invalid value harder to troubleshoot); typically, in modern programming, it either indicates that the value can be represented within the system's word size (which int is supposed to represent), or that the value can be represented within 32 bits (which is nearly always the size of int on x86 and x64 platforms).

Sized types also have the issue that the (theoretically) most well-known ones, the intX_t line, are only defined on platforms which support sizes of exactly X bits. While the int_leastX_t types are guaranteed to be defined on all platforms, and guaranteed to be at least X bits, a lot of people wouldn't want to type that much if they don't have to, since it adds up when you need to specify types often. [You can't use auto, either because it detects integer literals as ints. This can be mitigated by making user-defined literal operators, but that still takes more time to type.] Thus, they'll typically use int if it's safe to do so.

Or in short, int is intended to be the go-to type for normal operation, with the other types intended to be used in extranormal circumstances. Many programmers stick to this mindset out of habit, and only use sized types when they explicitly require specific ranges and/or sizes. This also communicates intent relatively well; int means "number", and intX_t means "number that always fits in X bits".

It doesn't help that int has evolved to unofficially mean "32-bit integer", due to both 32- and 64-bit platforms usually using 32-bit ints. It's very likely that many programmers expect int to always be at least 32 bits in the modern age, to the point where it can very easily bite them in the rear if they have to program for platforms that don't support 32-bit ints.


Conversely, the sized types are typically used when a specific range or size is explicitly required, such as when defining a struct that needs to have the same layout on systems with different data models. They can also prove useful when working with limited memory, using the smallest type that can fully contain the required range.

A struct intended to have the same layout on 16- and 32-bit systems, for example, would use either int16_t or int32_t instead of int, because int is 16 bits in most 16-bit data models and the LP32 32-bit data model (used by the Win16 API and Apple Macintoshes), but 32 bits in the ILP32 32-bit data model (used by the Win32 API and *nix systems, effectively making it the de facto "standard" 32-bit model).

Similarly, a struct intended to have the same layout on 32- and 64-bit systems would use int/int32_t or long long/int64_t over long, due to long having different sizes in different models (64 bits in LP64 (used by 64-bit *nix), 32 bits in LLP64 (used by Win64 API) and the 32-bit models).

Note that there is also a third 64-bit model, ILP64, where int is 64 bits; this model is very rarely used (to my knowledge, it was only used on early 64-bit Unix systems), but would mandate the use of a sized type over int if layout compatibility with ILP64 platforms is required.

Community
  • 1
  • 1
  • 1
    Could you provide an example of when "a generic integer is needed, which isn't required to have a specific range or size"? For example, if I loop through the days of the week, I must choose a data type that can hold up to 7 unique values so it needs to be at least 3 bits in size. But what would be an example of the use of an integer with _no_ size requirements? – snips-n-snails Apr 17 '17 at 22:14
  • @traal - I think you forget that there are other limitations than integer size to a system. On a computer with 16-bit ints, what are you going to loop over? Surely you will not have an array with a million objects without running out of memory long before bits in the int. – Bo Persson Apr 18 '17 at 09:15
  • @traal The most common ones I can think of are loop conditions and intermediate values used during a calculation. Sure, these can have specific ranges (and by extension, specific size requirements), but they're usually left vague so there's room to grow if necessary (it's easier to update a program to accomodate larger values if it already uses a larger type than necessary). – Justin Time - Reinstate Monica Apr 18 '17 at 16:25
  • The issue is basically that many people have the opposite approach to yours; where you prefer to impose strict limits, and only loosen them if necessary, many programmers prefer to use lax limits, and only tighten them if necessary. Where you would use `int16_t` because a value will always be in the range of `-10'000..10'000`, for example, many programmers would use `int`, and only change it to `int16_t` if they needed to save memory and didn't expect the requirement to ever grow larger than an `int16_t` could store. (This is less prevalent among programmers for more limited systems.) – Justin Time - Reinstate Monica Apr 18 '17 at 16:26
  • It also stems from people expecting operations at the system's native word size to be more efficient, potentially making them see the use of `int` as a harmless micro-optimisation. This is likely more common in the modern age, where the prevalent mindset is often that programs should be optimised for speed instead of memory usage when at all possible, due to modern systems typically having at least 4 GB (or more) even without counting swap files. There are a _lot_ of programs that'll greedily use as much memory as possible to increase speed, so using `int` is basically nothing in comparison. – Justin Time - Reinstate Monica Apr 18 '17 at 16:29
  • @JustinTime But `sizeof(int_fast32_t) >= sizeof(int)` [even on 64-bit platforms](http://stackoverflow.com/a/17490225/966071), so if you want lax limits, wouldn't you use `int_fast32_t` instead of `int`? – snips-n-snails Apr 18 '17 at 18:31
  • @traal The issue with that, though, is that `int_fast32_t` is 4x as long as `int`, but adds no further value in most cases. It doesn't clarify anything unless the code is explicitly intended to be portable to systems where `int` is less than 32 bits (in which case, `int32_t` or `int_least32_t` would probably be a better choice here), and actually introduces a little ambiguity (on some 64-bit platforms, 32-bit operations are just as fast as 64-bit ones, so between two systems with 32-bit `int`s, it's possible for `int_fast32_t` to be 32 bits on one and 64 bits on the other). – Justin Time - Reinstate Monica Apr 18 '17 at 19:27
  • If you're using `int`, the type's size and maximum range are typically unimportant for that particular variable's task. A lot of programmers don't want to take the time to specify implementation details that explicitly aren't important to the task at hand, so they won't use sized types unless they explicitly _need_ a specific range or size. – Justin Time - Reinstate Monica Apr 18 '17 at 19:35
  • (And yes, this means that at the core of most reasons, you'll find either complacency or laziness. This isn't a bad thing, though, since it actually helps to communicate intent more clearly. If you always use `int` as your go-to integral type, then any integral that _isn't_ `int` stands out more; a sized type, for example, immediately tells you that a variable explicitly _requires_ that range. Conversely, if you always use sized types, then they won't stand out to you, and you have to examine the code to determine if they actually _need_ that range, or if it's just a reasonable limit.) – Justin Time - Reinstate Monica Apr 18 '17 at 19:40
  • (For example, if I see nine `int`s and one `int32_t`, it immediately tells me that the nine are likely throwaway or intermediate values, while the one is important, and likely used for something like portability, cross-platform interactions, UTF-32 Unicode, or something that requires a variable that's _exactly_ 32 bits. If three are `int8_t`, five are `int16_t`, and two are `int32_t`, this significance isn't as readily apparent.) – Justin Time - Reinstate Monica Apr 18 '17 at 19:44
  • In the end, it mainly boils down to style and personal taste. Using `int` when implementation details are unimportant allows sized types to communicate a big ol' "**NOTICE ME!**", while always using sized types helps to communicate each variable's bounds more clearly. Both approaches have merit. – Justin Time - Reinstate Monica Apr 18 '17 at 19:46
  • @JustinTime *"Conversely, if you always use sized types, then they won't stand out to you, and you have to examine the code to determine if they actually need that range, or if it's just a reasonable limit."* How can it be one without the other? – snips-n-snails Apr 18 '17 at 23:02
  • @traal The difference is that a reasonable limit might be larger than the required range, especially if there are flexibility or forwards-compatibility concerns. If you know that a value is always `0..50`, you might use `int8_t` because it has the range you need; if you suspect that future changes may allow this value to climb to, say, 400 or so, then you might choose 500 as a limit, and use `int16_t` instead so you won't need to refactor in the future. In both cases, `int8_t` is sufficient for current needs; the former communicates this, while the latter doesn't. – Justin Time - Reinstate Monica Apr 19 '17 at 21:36
  • Here, it's more a case of not communicating your certainty as well; if you always use `int`, then any exceptions indicate absolute certainty that the variable _needs_ to be that size. If you always use sized types, then any given variable's type can either communicate absolute certainty that it _needs_ to be that size, or the uncertainty that it could be smaller, but you figured it's better safe than sorry. – Justin Time - Reinstate Monica Apr 19 '17 at 21:40
  • (Note that this only applies if you frequently use different sized types in the same block; if almost all of the variables in a block are `int16_t`, for example, then an `int32_t` will stand out just as much as an `int16_t` among `int`s, and indicate the same level of certainty.) – Justin Time - Reinstate Monica Apr 19 '17 at 21:41
  • 2
    @JustinTime So you're saying that `int` avoids the need to use a `typedef` for your `int8_t` (or spend time refactoring later when you need something bigger) and document why you chose `int8_t` instead of `int16_t` in the first place. That seems clumsy to me, so I guess we'll have to agree to disagree. – snips-n-snails Apr 19 '17 at 23:09
  • @traal It's definitely wasteful in a lot of cases, but usually not enough to cause problems unless you're working with a very large array or on a system with very little memory. People that do it typically consider the convenience (both avoiding future refactoring, and more easily communicating which variables have explicit size/range/portability requirements and which don't) to outweigh the waste, though. I use a mix of both when working on personal stuff, generally using sized types as hard limits when I feel it's appropriate, or `int` when it isn't. – Justin Time - Reinstate Monica Apr 21 '17 at 15:29
-3

There are several reasons. One, these long names make the code less readable. Two, you might introduce really hard to find bugs. Say you used int_fast16_t but you really need to count up to 40,000. The implementation might use 32 bits and the code work just fine. Then you try to run the code on an implementation that uses 16 bits and you get hard-to-find bugs.

A note: In C / C++ you have types char, short, int, long and long long which must cover 8 to 64 bits, so int cannot be 64 bits (because char and short cannot cover 8, 16 and 32 bits), even if 64 bits is the natural word size. In Swift, for example, Int is the natural integer size, either 32 and 64 bits, and you have Int8, Int16, Int32 and Int64 for explicit sizes. Int is the best type unless you absolutely need 64 bits, in which case you use Int64, or if you need to save space.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • 10
    "`int` cannot be 64 bits" - I don't think this is true. There's nothing that says types with 8, 16, and 32 bits have to exist. – aschepler Apr 16 '17 at 20:38
  • From header `stdint.h` you can declare integer, which is 64 bit - `int64_t` – Nikola Vasilev Apr 16 '17 at 20:46
  • 5
    Implementers tend to *prefer* to provide predefined types covering sizes of 8, 16, 32, and 64 bits, but the standard doesn't require it. I've worked with C implementations with 64-bit `int`. Both had 8-bit `char`; one had 32-bit `short` and another had 64-bit `short`. Neither had a 16-bit integer type, and the latter had no 32-bit integer type. – Keith Thompson Apr 16 '17 at 20:52
  • 3
    int is equally likely to introduce hard to find bugs. If you count over the size of your intended maximum, you should have written an assert. No fixed size type will give you any advantage over another. – rubenvb Apr 16 '17 at 20:54
  • 2
    There is no 8 bit minimum. You can have a 16 bit char meaning the smallest type on that system is at least 16 bits. – NathanOliver Apr 16 '17 at 21:18
  • The standard requires, due to the specified ranges, that `signed char` be a minimum of 8 bits, `short` and `int` be a minimum of 16 bits, `long` be a minimum of 32 bits, and `long long` be a minimum of 64 bits, with each of these being _at least_ as large as the one before it; it specifies no maximum size, and even allows both `signed char` and `long long` to be, say, 128 bits. (`unsigned` types are required to have the same size & alignment as their `signed` counterparts, and both `signed char` & `unsigned char` are required to have the same size & alignment as `char`.) – Justin Time - Reinstate Monica Apr 17 '17 at 20:07