23

I'm excited to use auto variables in my C++ programs. I know variables declared with auto use template rules to deduce variable types, but I'm confused as to how that works for numeric types. Suppose I have:

auto foo = 12;

The type for foo could reasonably be int or even unsigned char. But suppose that later in my program I do some math and assign foo a value of 4 billion. At that point, I would want foo to become type unsigned int or perhaps long.

How can compilers anticipate values that will be assigned later in the program?

4444
  • 3,541
  • 10
  • 32
  • 43
David Lobron
  • 1,039
  • 2
  • 12
  • 21
  • 11
    The value `12` is an int... unless it doesn't fit in an int. Then it would be determined by the promotion rules. – Eljay Feb 26 '18 at 15:03
  • 5
    "The type for foo could reasonably be int or even unsigned char" no it cannot – Slava Feb 26 '18 at 15:04
  • 1
    The macros in `` can be used to specify a literal to a particular type. http://en.cppreference.com/w/cpp/header/cstdint – Eljay Feb 26 '18 at 15:04
  • 6
    I would just declare as an int, use auto where it really matters (long type names, non-conflicted types...) – Max Feb 26 '18 at 15:12
  • No luck searching? e.g. https://stackoverflow.com/questions/208433/how-do-i-write-a-short-literal-in-c / https://stackoverflow.com/questions/8108642/type-of-integer-literals-not-int-by-default / https://stackoverflow.com/questions/2304732/how-do-i-specify-an-integer-literal-of-type-unsigned-char-in-c – underscore_d Feb 27 '18 at 10:36
  • "How can compilers anticipate values that will be assigned later in the program?" - As pointed out in the answers below, C++ doesn't do this. However, there are languages that can. [Haskell](https://www.haskell.org/), [Rust](https://www.rust-lang.org/), and the [ML family](https://en.wikipedia.org/wiki/ML_(programming_language) are a few examples. Read about the [Hindley-Milner Type System](https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system) to learn how they do it. – 8bittree Mar 09 '18 at 22:16
  • 1
    @Eljay "The value `12` is an int... unless it doesn't fit in an int". To be pedantically clear, the "unless" applies to integer literals in general, but not to `12`. `12` is guaranteed to fit in an `int`. – Keith Thompson Jun 29 '21 at 19:07

7 Answers7

51

The compiler works with the information present which in your case is the integer literal 12. So it deduces the foo to be of type int. It does not anticipate anything. You can use the appropriate integer literal suffix:

auto foo = 12ul;

to force the foo to be deduced as unsigned long. You can't define the variable to be of type int and then down the line expect the compiler to somehow change it into another type just because you assigned a different value that will not fit into previously used type. If you did that it would simply result in integer overflow which is undefined behavior.

For more info on the subject check out the auto specifier and auto type deduction reference.

Ron
  • 14,674
  • 4
  • 34
  • 47
  • Thanks! This answer and the next one totally explain it. I will bookmark the rules page: http://en.cppreference.com/w/cpp/language/integer_literal. – David Lobron Feb 26 '18 at 18:00
  • 2
    @DavidLobron, answers don't always order the same as time goes on. For sake of future readers, can you specify which other answer is useful by either mentioning the user who posted it, or linking it it? (You can get a link by clicking on the share button at the bottom of the post.) – David Feb 26 '18 at 19:01
  • 2
    "You can't define the variable to be of type int and then down the line expect the compiler to somehow change it into another type" Actually the OP simply expected C++ to provide real type-inference. If you use the variable to which you have assigned `12` as a double, then the compiler would decide to use `double` as type, or `long` or `int` depending on the uses. This can be done (for example Haskell does this). The type does not change in any way. But in C++ literals are monomorphic and so this cannot work. – Bakuriu Feb 26 '18 at 21:21
  • @David: Oops, will do. The other answer I found particularly helpful was: https://stackoverflow.com/a/48991510/2199456. BTW, I highly recommend the book "Effective Modern C++", by S. Meyers. – David Lobron Feb 27 '18 at 15:29
23

"The type for foo could reasonably be int or even unsigned char"

No it cannot. Every expression in C++ has a type, and that is clearly defined in the language. In your case the expression is an integer literal, so the type corresponds to the literal. What type it is specifically, is defined by the rules:

The type of the literal

The type of the integer literal is the first type in which the value can fit, from the list of types which depends on which numeric base and which integer-suffix was used.

no suffix - int, long int, long long int(since C++11)

"How can compilers anticipate values that will be assigned later in the program?"

It cannot. The type is determined when you declare the variable, and it cannot be changed later.

Community
  • 1
  • 1
Slava
  • 43,454
  • 1
  • 47
  • 90
10

The type for foo could reasonably be int or even unsigned char

It could be many things, but it actually is only one thing.

The integer literal 12 has type int.

Period.

But suppose later in my program, I do some math and assign foo a value of 4 billion. At that point, I would want foo to have type unsigned int or perhaps long. How can compilers anticipate values that will be assigned later in the program?

They can't, and they don't. The type of foo won't change. foo doesn't have type auto (there is no such thing); it has type int. Your program henceforth will be as if you'd written int foo = 12;. The deduction/automation ends there.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
6

At that point, I would want foo to have type unsigned int or perhaps long.

That's not the way the language works. A variable cannot have its type changed at runtime. If you define and initialise a variable as auto foo = 12;, that means exactly the same thing as int foo = 12;, regardless of any future assignments, because the type of 12 is int.

How can compilers anticipate values that will be assigned later in the program?

They don't have to. Values that are assigned later will be converted to the type of the variable. If the value is out of range for that type, the exact rules depend on the types you're dealing with.

4

My advice is that this is not a good place to use auto. You know what factors determine the type you need, and they can’t be deduced from the immediate context. (However, if you can write your variables as single static assignments, that won’t ever happen.)

If you know that the variable needs to be able to hold a value of at least 4 billion, declare it as unsigned long or long long int. Or, if you really want to code defensively against unfortunate choices for the width of those types (such as platforms where long is 32 bits wide to support legacy code, but the native word size is 64 bits), declare it as uint_fast32_t or int_fast64_t. Or if you want the smallest, not the fastest, uint_least32_t. (Sometimes, the fastest code is the one that keeps the most values in the cache!)

If what you really want is the fastest signed or unsigned type that can hold a value of 4 billion, say what you mean!

Davislor
  • 14,674
  • 2
  • 34
  • 49
1

Personally, I wouldn't use auto for these immediate constants, but where the value comes from another method call, or assigned from another variable.

If you do want a way to define a variable to match another variable's type, so you can inter-operate with it, but you want to assign a constant value, then use decltype to ensure its size is compatible:

decltype(otherVar) myVar = 1234;
myVar += otherVar;  // will work just as well as otherVar += myVar

In any case, the type is specified by the literal constant, and an undecorated 12 will define an int.

However, you can decorate your constant with U, to make it unsigned, or L to make it long, or even LL to make it super-long. There is no equivalent to force it short or char, unfortunately!

Gem Taylor
  • 5,381
  • 1
  • 9
  • 27
0

Like all other answers, there is no magic.

But there are some other ways you can upfront specify the desired type.

auto foo = long(12);
std::cout << typeid(foo).name() << std::endl;

auto anotherFoo = long long (12);

//Some compilers may not allow multiple words like "long long"
//In that case you can use using keyword.
using llong = long long;
auto someOtherFoo = llong (12);
Pavan Chandaka
  • 11,671
  • 5
  • 26
  • 34
  • *"Some compilers may not allow multiple words like "long long""* I'm fairly sure `long long(12)` is illegal. – HolyBlackCat Jun 29 '21 at 18:46
  • But then you're specifying the type anyway. I see no advantage for `auto foo = long(12);` over `long foo = 12;`. – Keith Thompson Jun 29 '21 at 19:04
  • Readability or may be consistency in OP's code base. The OP s expectation is, wants to declare auto initially and eventually pick up the long data. – Pavan Chandaka Jun 29 '21 at 19:30