C++ – How to Use ‘auto’ in C++17 Without Replacing with Actual Type?

autoc++c++17

I have always thought that auto in C++17 is a placeholder for the actual data type, but it appears not so in the following:

tuple<int, int> permute(int a, int b){return {b,a};}
int main(){
   auto [x,y] = permute(2,5);
   cout<<x<<","<<y<<endl;
   tuple<int, int> {x,y} = permute(2,5);
   cout<<x<<","<<y<<endl;
   return 0;
   }

Why auto requires [ , ] while a tuple requires curly brackets?

EDIT: As pointed out below [ , ] is C++17 Structured binding declaration. Nonetheless, my question remains: why can't I replace tuple<int, int> {x,y} with auto {x,y} ?

Best Answer

auto [/*...*/] is a completely different syntax construct from other declarations. (And it is supported only since C++17 btw.)

It is a so-called structured binding, not a simple declaration of a variable. The auto keyword and brackets are part of that special syntax. They are not (directly) the type of a variable that is being declared or the initializer for such a variable.

The auto is used in the behavior of a structured binding declaration to provide the declaration of an (unnamed) variable that is initialized from the right-hand side as if by

auto _unnamed = permute(2,5);

in your example and auto will be deduced to std::tuple<int,int>. x and y then behave like references to the first and second element of this _unnamed variable.

The structured binding does not inherently have any connection with std::tuple. Any array type, aggregate type or type that provides a certain std::tuple_element/std::get interface can be used with structured bindings.

As for why specifying the explicit type directly instead of auto in this form of declaration isn't allowed, the only reason given in the proposal in section 3.6 is that one can always explicitly specify the type on the right-hand side if desired:

 auto [x,y] = tuple<int,int>(permute(2,5));

I do not think there is any other reason that it hasn't been allowed. This seems a bit like simply personal preference of the proposal authors.


In tuple<int, int> {x,y} the braces are the initializer of an object, namely a temporary object of type tuple<int, int> being created in that expression.

Note that this is not a declaration at all. It uses the x and y from the previous structured binding declaration to initialize the temporary object. It doens't declare x or y. You then assign to this temporary object the result of permute which effectively doesn't do anything, because that object is immediately destroyed again at the end of the expresison.

A simple declaration would look like this:

tuple<int, int> t = permute(2,5);

and this wouldn't allow accessing the elements by own names directly. You would have to introduce names for them as references specifically if you don't use the structured binding syntax:

int& x = std::get<0>(t);
int& y = std::get<1>(t);

why can't I replace tuple<int, int> {x,y} with auto {x,y} ?

tuple is not in any way a special type in C++. Just because you use braces has no implication that you want to form a tuple. You can write S{x,y} for any suitable aggregate type S and it would be weird if the language preferred tuple over other possible types.

In many other languages a tuple type is not only a library type like in C++, but is instead more fundamentally connected to the core language. In this case the language will usually provide special syntax to form tuples, e.g. as (x, y). In C++ tuples are not built-in types, have no special meaning in the core language and there is no syntax dedicated to them.

You can however write tuple{x,y}. The element types can be deduced via CTAD (class template argument deduction) since C++17.

That being said, there is indeed a special type in the standard library that is preferred whenever braces are used (and one can argue about whether it was a good decision to design it like that). This preferred type is however not std::tuple, but instead std::initializer_list, which is a magic type that behaves similar to a copyable reference to an implicitly-created array, i.e. it works only with homogeneous element types and doesn't provide value semantics when being copied.

So for example in

auto a = {x, y};

the type of a will be deduced to std::initializer_list<int>. This is meant to be used primarily for iteration in range-for loops and for passing to constructors of containers. So even if preferring one type over the other wasn't weird anyway, C++ already made a choice for another type other than tuple.