C++ Auto Keyword. Why Is It Magic

C++ auto keyword. Why is it magic?

auto was a keyword that C++ "inherited" from C that had been there nearly forever, but virtually never used because there were only two possible conditions: either it wasn't allowed, or else it was assumed by default.

The use of auto to mean a deduced type was new with C++11.

At the same time, auto x = initializer deduces the type of x from the type of initializer the same way as template type deduction works for function templates. Consider a function template like this:

template<class T>
int whatever(T t) {
// point A
};

At point A, a type has been assigned to T based on the value passed for the parameter to whatever. When you do auto x = initializer;, the same type deduction is used to determine the type for x from the type of initializer that's used to initialize it.

This means that most of the type deduction mechanics a compiler needs to implement auto were already present and used for templates on any compiler that even sort of attempted to implement C++98/03. As such, adding support for auto was apparently fairly easy for essentially all the compiler teams--it was added quite quickly, and there seem to have been few bugs related to it either.

When this answer was originally written (in 2011, before the ink was dry on the C++ 11 standard) auto was already quite portable. Nowadays, it's thoroughly portable among all the mainstream compilers. The only obvious reasons to avoid it would be if you need to write code that's compatible with a C compiler, or you have a specific need to target some niche compiler that you know doesn't support it (e.g., a few people still write code for MS-DOS using compilers from Borland, Watcom, etc., that haven't seen significant upgrades in decades). If you're using a reasonably current version of any of the mainstream compilers, there's no reason to avoid it at all though.

More recent revisions of the standard have added a few new places that auto can be used. Starting with C++14, you can use auto for the type of a parameter to a lambda:

    [](auto s) { return s + 1; }

This does essentially the same thing as the example above--even though it doesn't explicitly use template syntax, this is basically a template that deduces the type of the parameter, and instantiates the template over that type.

That was convenient and useful enough that in C++20, the same capability was added for normal functions, not just lambdas.

But, just as before all of this really comes down to using the same basic type deduction mechanism as we've had for function templates since C++98. auto allows that to be used in more places, and more conveniently, but the underlying heavy lifting remains the same.

Confusion about auto keyword in C++

auto is not a storage class. It used to be, before C++11. But it was completely useless, so the keyword was re-purposed to allow automatic type inference. So when you say:

auto int ret = foo();

You are basically declaring the object to have 2 types (or possibly the same type twice), and that is an error. And when you say:

auto ret = foo();

The type of ret is determined by whatever the function foo returns, which is int* in this case.

Why do I need to explicitly write the 'auto' keyword?

Dropping the explicit auto would break the language:

e.g.

int main()
{
int n;
{
auto n = 0; // this shadows the outer n.
}
}

where you can see that dropping the auto would not shadow the outer n.

Is C++11 auto keyword exactly defined for all cases? Or: how does auto know what I intend?


What will a be?

int, since that's the type of 4.

Will x be of type *B or of type *A?

B*, since that's the type of new B().

Is there an exact list of the auto behaviour?

Usually, it's the type of the initialiser; unless that's a reference type, in which case it's the underlying object type. There are a few other wrinkles for unusual types like arrays, as mentioned in the comments.

will it always be the exact same type (with the exact same bit length!) on each compiler and each architecture?

In most cases, the initialiser has a well-defined type, and that determines the type deduced by auto.

If the initialiser is an integer literal, then the type might depend on the platform; for example, 1000000 might be int on a 32-bit platform, but long on a 16-bit platform.

auto keyword strange behavior in C++11

The auto storage class specifier is not "useless and deprecated in C++11," it has been removed entirely. The auto keyword is no longer a storage class specifier and cannot be used as one.

In C++11, auto is a simple type specifier.

Does auto keyword always evaluates floating point value as double?

The issue is that your code is using ::sin instead of std::sin (and the same for cos). That is, you’re using the sin function found in the global namespace.

std::sin is overloaded for float. But ::sin isn’t, and it always returns a double (because ::sin is the legacy C function, and C doesn’t have function overloading).

Use std::sin and std::cos in your code to fix the issue.

where auto keyword can't determine the type in C++

"Can't" is a strong word. After all, lambda parameters can use auto (in C++14). It's not so much "can't" as "doesn't". And perhaps "won't".

The question ultimately comes down to this: what does this actually do?

void foo(auto x)
{
std::cout << x << std::endl;
}

auto deduction is ultimately based on providing an initializing expression, which is used to deduce the actual type. This declaration contains no initializing expression.

C++ is a statically typed language; that means the compiler must be able to determine the type of every expression and object at compile time. From just the above, the compiler can deduce nothing.

So how can the compiler know that std::cout << x is legal C++ syntax? It can only tell that if there is an operator<< overload that takes std::cout and x. It can't figure out what is being called without first knowing the type of x. Which it doesn't; it can be different for different invocations of the function.

However, there is one C++ construct where the above makes sense: a template. This is exactly what using auto in lambda parameters does; it implicitly transforms the function into a template function. So [](auto x) {return x;} effectively becomes an operator something like this:

template<typename T>
auto operator()(T x) {return x;}

However, this conversion doesn't just fall out of having auto as a deduction syntax. It has to be something the standard is explicitly written to require. And, with the exception of C++14 generic lambdas, it doesn't.

Concepts TS includes this facility. However, this is merely an extension of the ability to use concepts in function parameter lists at all. That is, they already have a way to make a function implicitly create a template function, so they just added auto as essentially a case of "a concept that accepts any type".

But this was explicitly excluded from the C++20 version of concepts. The general reason for this exclusion is that, up until this point, template functions could be detected because they always had to have some special syntax. Namely, the inducer template<args>. With the Concepts TS version, there is concern that people will write template functions without realizing it.

And template functions behave differently from non-template functions. A template function is a family of functions, so you can't get a pointer to the family itself. You'd have to explicitly instantiate the template to get a pointer to that particular function.

How much is too much with C++11 auto keyword?

I think that one should use the auto keyword whenever it's hard to say how to write the type at first sight, but the type of the right hand side of an expression is obvious. For example, using:

my_multi_type::nth_index<2>::type::key_type::composite_key_type::
key_extractor_tuple::tail_type::head_type::result_type

to get the composite key type in boost::multi_index, even though you know that it is int. You can't just write int because it could be changed in the future. I would write auto in this case.

So if the auto keyword improves readability in a particular case then use it. You can write auto when it is obvious to the reader what type auto represents.

Here are some examples:

auto foo = std::make_shared<Foo>();   // obvious
auto foo = bla(); // unclear. don't know which type `foo` has

const size_t max_size = 100;
for ( auto x = max_size; x > 0; --x ) // unclear. could lead to the errors
// since max_size is unsigned

std::vector<some_class> v;
for ( auto it = v.begin(); it != v.end(); ++it )
// ok, since I know that `it` has an iterator type
// (don't really care which one in this context)


Related Topics



Leave a reply



Submit