(I accidentally answered the wrong question to a related question of this Q&A, after mis-reading it; I'm moving my answer to this question instead, the question which my answer actually addressed)
P0007R1 introduced std::as_const
as part of C++17. The accepted proposal did not mention rvalues at all, but the previous revision of it, P0007R0, contained a closing discussion on rvalues [emphasis mine]:
IX. Further Discussion
The above implementation only supports safely re-casting an l-value as
const (even if it may have already been const). It is probably
desirable to have xvalues and prvalues also be usable with as_const,
but there are some issues to consider.
[...]
An alternative implementation which would support all of the forms
used above, would be:
template< typename T >
inline const T &
as_const( const T& t ) noexcept
{
return t;
}
template< typename T >
inline const T
as_const( T &&t ) noexcept( noexcept( T( t ) ) )
{
return t;
}
We believe that such an implementation helps to deal with lifetime
extension issues for temporaries which are captured by as_const, but
we have not fully examined all of the implications of these forms. We
are open to expanding the scope of this proposal, but we feel that
the utility of a simple-to-use as_const is sufficient even without the
expanded semantics.
So std::as_const
was basically added only for lvalues as the implications of implementing it for rvalues were not fully examined by the original proposal, even if the return by value overload for rvalue arguments was at least visited. The final proposal, on the other hand, focused on getting the utility in for the common use case of lvalues.
P2012R0 aims to address the hidden dangers of range-based for loops
Fix the range‐based for loop, Rev0
The range-based for loop became the most important control structure
of modern C++. It is the loop to deal with all elements of a
container/collection/range.
However, due to the way it is currently defined, it can easily
introduce lifetime problems in non-trivial but simple applications
implemented by ordinary application programmers.
[...]
The symptom
Consider the following code examples when iterating over elements of
an element of a collection:
std::vector<std::string> createStrings(); // forward declaration
…
for (std::string s : createStrings()) … // OK
for (char c : createStrings().at(0)) … // UB (fatal runtime error)
While iterating over a temporary return value works fine, iterating
over a reference to a temporary return value is undefined behavior.
[...]
The Root Cause for the problem
The reason for the undefined behavior above is that according to the
current specification, the range-base for loop internally is expanded
to multiple statements: [...]
And the following call of the loop:
for (int i : createOptInts().value()) … // UB (fatal runtime error)
is defined as equivalent to the following:
auto&& rg = createOptInts().value(); // doesn’t extend lifetime of returned optional
auto pos = rg.begin();
auto end = rg.end();
for ( ; pos != end; ++pos ) {
int i = *pos;
…
}
By rule, all temporary values created during the initialization of the
reference rg
that are not directly bound to it are destroyed before
the raw for loop starts.
[...]
Severity of the problem
[...]
As another example for restrictions caused by this problem consider
using std::as_const()
in a range-based for loop:
std::vector vec; for (auto&& val : std::as_const(getVector())) {
… }
Both std::ranges
with operator |
and std::as_const()
have a
deleted overload for rvalues to disable this and similar uses. With
the proposed fix things like that could be possible. We can definitely
discuss the usability of such examples, but it seems that there are
more example than we thought where the problem causes to =delete
function calls for rvalues.
These gotchas is one argument to avoid allowing an std::as_const()
overload for rvalues, but if P2012R0 gets accepted, such an overload could arguably be added (if someone makes a proposal and shows a valid use case for it).
as_const
comes right into the place to support that. Even if there seems no use case, what danger is there to support it? If there's a real danger, why not also forbidstatic_cast<const Foo&&>(getFoo())
then? – Crownworkop=
overloaded.. which in all humbleness is quite an overkill :) – Crownworkas_const_ref
could be added where it's allowed also for rvalues IMHO. Theref
at the end would make it clear that it introduces an indirection. – Crownworkas_const_ref
would be used frequently enough to justify it, so that's probably why they didn't do it. How often do people do COW (esp. an approximate one)?(!) – Pneumatology