auto{x} != auto(x)
auto{x} != auto(x)Recently it was asked: What’s the difference between the expressions auto(x)
and auto{x} in C++23?
The construct auto(x) arrived via
P0849 “decay-copy in the language”.
We could already write direct-initialization to a named type as either a declaration
or a cast-expression:
T y(x); // declaration
return T(x); // cast-expression
P0849 just extended this syntax to work for a placeholder type as well:
auto y(x); // declaration
return auto(x); // cast-expression
Both of the latter lines mean “Deduce the type of auto from the type of x (decaying to
a non-array object type if necessary) — let’s call that type T — and then explicitly cast x to
that type exactly as if the user had written T in place of auto.”
This usually means we’re just making a copy of x using its copy constructor.
If x stands for an xvalue expression, we’re calling the move constructor, and if
x is a prvalue, we’re probably not doing anything at all. auto(x) is simple.
But auto{x} is more complicated, because curly braces produce an initializer list.
This means the same thing as T{x}: “given the list of elements {x}, make me a T
with those elements.” As [dcl.init.list]/3.7 shows,
that’s not always the same thing as “make me a copy of x.” Godbolt:
auto paren() {
std::vector<std::any> v;
return auto(v);
}
auto curly() {
std::vector<std::any> v;
return auto{v};
}
The former means “make an empty vector, then return a copy of that vector (with no elements).” The latter means “make an empty vector, then return a vector containing that vector (with one element).”
-
As of this writing, MSVC gets this wrong: it calls the copy constructor in both cases. But [dcl.init.list]/3.7 (last improved by CWG2638) makes it very clear that MSVC is in the wrong.
-
return (x);implicitly moves fromx(see P2266 “Simpler implicit move”), butreturn auto(x);does not. This makes sense, becausereturn T(x);doesn’t move-fromxeither. Remember, allautodoes here is hold the place of an explicitly specifiedT.
Consider also these variations:
auto p = (v); // copy
auto c = {v}; // initialize a new vector of 1 element
auto p(v); // copy
auto c{v}; // initialize a new vector of 1 element (MSVC gets this wrong)
Of course vector<any> is a pathological case. Its benefit is that it’s also a simple case
using only STL types, in case you ever need to demonstrate the difference between (x) and {x}
to anyone else.
The takeaway: As always, you should use curly braces when you have a sequence of elements (such as when initializing an aggregate or a container); if you aren’t in that situation (such as when you’re writing generic code) you should use ordinary parentheses. See “The Knightmare of Initialization in C++” (2019-02-18).
I suspect there is no situation where it ever makes sense to use auto{x} in real code.
But I’m glad it exists in the language, for symmetry and consistency with T{x}.
Note that all of these lines—
auto a(1,2,3);
auto a = auto(1,2,3);
auto a{1,2,3};
auto a = auto{1,2,3};
—are invalid C++. You can never direct-initialize auto with multiple arguments.
However, both of the following copy-initializations are legal and silly:
auto i = (1,2,3); // comma operator; i is int
auto i = {1,2,3}; // i is initializer_list<int>
The latter is a historical accident which is supported these days, as far as I know, only so that we can specify the behavior
of for (int i : {1,2,3}) without having to write a special case into [stmt.ranged].
(N3922 “New rules for auto deduction from braced-init-list”
is the paper that removed auto a{1,2,3} from the language. N3922 came in 2014, at the height of the “Almost Always Auto” and “Uniform Initialization”
fads; it was widely assumed that newbies would write auto a{1,2} and shoot themselves in the foot, but writing auto a = {1,2}
wasn’t so attractive to newbies and thus wasn’t treated so urgently as a footgun. At the same time, N3922 changed both
auto a{1} and auto a = {1} to deduce int rather than initializer_list<int>. Only auto a = {1,2} remains as a special case
inconsistent with the rest of the language. N3912 §1 says
this special case will be useful to “advanced users,” which I think in hindsight was a bad justification for keeping it.)
