Introduction
The auto
keyword in C actually predates C. It first appeared in B for use when declaring automatic (meaning “automatically created in the stack frame of the enclosing function”) variables.
B is a typeless language in that all variables are integers, so, for example, the following declares some variables in B:
auto a, b, c;
C, of course, is a typed language; but to make migration of code from B to C easier, Ritchie:
- Adopted
auto
into C. - Made
int
the default type (meaning if a type were omitted in a declaration, it was assumed to beint
).
Hence, the above declaration means the same thing in C as it does in B. However, as a consequence, in C:
void f() {
auto int x; // Explicit "auto" adds nothing ...
int y; // ... since "auto" is the default.
// ...
the declaration of y
is the same as x
since auto
is the default storage class (as opposed to either static
or extern
), so explicit use of auto
adds nothing.
Since int
requires typing one fewer character than typing auto
, being explicit about int
eventually became common practice. (In C99, int
stopped being assumed.) Hence there was no reason to specify auto
explicitly in new code and its use faded away.
For those interested in a much more detailed history of C, see The Development of the C Language, Dennis M. Ritchie, April, 1993.
Resurrection
Eventually, C++11 repurposed auto
to mean “automatically deduced type” where the type of the variable being declared is deduced based on the type of its initializer:
double sqrt( double );
auto r = sqrt( x ); // deduces double
As of C23, the repurposed auto
of C++11 was back-ported to C (more or less, but mostly less).
auto
in C23
There are a few differences between C++11’s and C23’s auto
. In general, auto
in C is a weaker version of that in C++, specifically:
- In C++,
auto
can declare more than one variable in the same declaration; in C23, it can’t. - In C++,
auto
can be used with*
to deduce a pointed-to type; in C23, it can’t. - In C++,
auto
can not be used for its original purpose of being a storage class; in C23, it can. - In C++,
auto
can be used as the return type of aninline
(orconstexpr
) function; in C23, it can’t. - In C++,
auto
can be used as a function parameter type as a shorthand for templates; in C23, it can’t. - In C++23,
auto
can be used to get an explicit copy of an object; in C23, it can’t.
C23’s auto
is modeled on the existing gcc
__auto_type
extension.
No Multiple Declaration Support
An example of the first difference is:
auto x = get_x(), y = get_y(); // C++: OK; C: error
Why didn’t the C committee allow auto
to declare multiple variables in the same declaration? Because __auto_type
doesn’t allow it and the committee simply wanted to standardize the existing __auto_type
.
No *
Support
An example of the second difference is:
auto *p = strchr( s, '.' ); // C++: 'char'; C: error
auto q = strchr( s, '.' ); // C++ & C: 'char*'
Why didn’t the C committee allow *
? First, in C++, you need to be able to do auto&
, that is optionally use references with auto
. Since &
is allowed, *
is also allowed in C++ for symmetry.
C, of course, doesn’t have references so there’s no symmetry issue. And __auto_type
doesn’t allow *
.
Still a Storage Class
An example of the third difference is:
auto int i; // C++: error; C: 'int i'
That is auto
, when paired with an explicit type, retains its original purpose as a storage class (like either static
or extern
). Since the C committee is a conservative group, my guess is that they wanted to maintain backwards compatibility even with the likely trivially few C programs that use the original auto
explicitly.
No Function Support
Examples of the fourth and fifth differences are:
inline auto max( auto x, auto y ) { // C++: OK; C: error
return x > y ? x : y;
}
In C++, being able to have the compiler deduce a return type helps a lot since it can vary depending on the type(s) of the arguments. Also in C++, allowing auto
for parameters is a light-weight version of templates since the above is equivalent to:
template<typename T1, typename T2>
inline auto max( T1 x, T2 y ) {
return x > y ? x : y;
}
Since C doesn’t have templates, there was no compelling reason to allow auto
for functions.
Allowing
auto
for functions would be a nice way to add a “lite” version of templates to C without all the boilerplatetemplate
syntax. But I wouldn’t hold my breath for the C committee to adopt it.
No Copy Support
In C++23, you can use auto(x)
to get a copy of x
without having either to know its type or to name it. Again, since C doesn’t have templates (which is why you wouldn’t know its type), there was no reason to allow this in C.
If you’re interested in the use-case for
auto(x)
in C++, you can read its original proposal.
Uses for auto
in C
Since auto
in C is a weaker version of that in C++, is auto
in C really useful? It’s useful in a few cases:
- Future-proofing code.
- Type-agnostic macros.
- Coordination with
_Generic
.
Future-Proofing
Consider a function to generate a unique ID number every time it’s called:
unsigned get_new_id();
void f() {
unsigned id = get_new_id();
// ...
What if at some point you need to change the return type of get_new_id()
to, say, unsigned long
or UUID? You’d have to change the types of all variables that stored an ID throughout your program.
While the traditional way to solve this is to use a typedef
for an ID, you can now just use auto
and it will always be the right type:
auto id = get_new_id(); // future-proof
Type-Agnostic Macros
Sometimes in a macro, it’s convenient to be able to declare a variable that’s the same type as one of the arguments, for example:
#define SWAP(X,Y) \
do { \
auto const temp = (X); \
(X) = (Y); \
(Y) = temp; \
} while (0)
will work for any type (even struct
s).
Coordination with _Generic
When a macro can return a value of a varying type via _Generic
, auto
allows you to always get the matching type, for example:
#define div(N,D) \
_Generic( (N) + (D), \
int : div, \
long : ldiv, \
long long: lldiv \
)( (N), (D) )
auto result = div( 38484848448, 448484844 );
The type of result
will be the same as the return type of whichever function the div()
macro ends up expanding into.
Remember that
_Generic
does not evaluate expressions. The use of(N) + (D)
causes the compiler to determine what the common type ofN
andD
are via the usual arithmetic conversions. For example, if one isint
and the other islong
, theint
will be promoted tolong
, the result will belong
, and that’s the type that’s selected.
Conclusion
Despite C23’s auto
being a weaker version of C++’s auto
, it has its few niche uses. Perhaps some day, the C committee will adopt more of C++’s auto
.
Epilogue
You may be aware that C23 also added typeof
as another way to deduce types. So what about typeof
? How does it differ from auto
? Why did C23 add both? That’s a story for another time.