auto(...)
has the benefit that it always clearly communicates that a copy is needed and intended.
This is one of the motivations for the original proposal, P0849R8: auto(x)
: decay-copy in the language
While you could write
// assuming non-explicit copy constructor
auto copy = t.child;
printChild(copy);
... it's not obvious to the reader that the extra variable is needed (or not).
By comparison, printChild(auto(t.child));
is expressing the intent to copy very clearly, and it works even if you don't know the type of copy
or if the type is very lengthy and annoying to spell out.
Of course, since printChild
accepts any T
by value, you could just write printChild(t.child)
and let the copy take place implicitly.
However, in generic code, you typically work with forwarding references or other kinds of references, not values.
You don't want to pass things by value if you don't know whether they're small types.
A motivating example comes from the proposal itself (slightly adapted):
void pop_front_alike(Container auto& x) {
std::erase(x, auto(x.front()));
}
Note: the copy of x.front()
is needed here because erasing vector contents would invalidate the reference obtained from x.front()
and passed to std::erase
.
Outside of templates, you often should pass stuff by rvalue reference as well, as recommended by CppCoreGuidelines F.18: For “will-move-from” parameters, pass by X&& and std::move the parameter:
// CppCoreGuidelines recommends passing vector by rvalue ref here.
void sink(std::vector<int>&& v) {
store_somewhere(std::move(v));
}
struct S {
std::vector<int> numbers;
void foo() {
// We need to copy if we don't want to forfeit our own numbers:
sink(numbers); // error: rvalue reference cannot bind to lvalue
sink(std::vector<int>(numbers)); // OK but annoying
sink(auto(numbers)); // :)
}
};
Last but not least, you can simply look at the C++20 standard.
There are 43 occurrences of decay-copy in the document, and any use of decay-copy can usually be replaced with auto(x)
.
To name some examples,
std::ranges::data(t)
may expand to decay-copy(t.data())
, and
- the
std::thread
constructor applies decay-copy to each argument.
auto
at all here but that is my personal preference. – Ricottaerase
example. You cannot take the value to be erased by reference because the reference will be invalidated after erasure. That particular example has nothing to do with constructors. – Pairoar