Consider the following example code in C++ >=17:
struct A{
A() = default;
A(const A&) = delete;
};
const A f(){ return A{}; }
int main(){
const A& a = f(); // OK
// A& b = f(); // Error: cannot convert 'const A' to 'A&'
const A c = f(); // OK: Copy elision
A d = f(); // OK!?
}
The class A
is non-copyable, but because of the mandatory copy-elision, we can put the result of f()
into variables. According to this page in cppreference.com, the above behavior is perfectly legitimate, since it is specified that the const
quantifier on the returning value is ignored when copy-elision happens.
However, this behavior seems very counterintuitive to me. Since A
is non-copyable, I feel like there should be no way to turn const A
into A
(except perhaps when you have A::A(const A&&)
constructor). Is this a well-thought decision, or is this considered a defect in language specification?
(I have encountered this problem when trying to implement my own type-erasure class. The whole purpose of me specifying const
on the return value of function f()
was to prevent the user from getting a non-const
lvalue reference of the object, but this specification seems to open a hole.)
Edit: This example might show the counter-intuition more clearly: Let's consider a class A
that is movable but not copyable.
struct A{
A() = default;
A(const A&) = delete;
A(A&&) = default;
};
int main(){
// C++14: move / C++17: copy elision
A a = A{};
// C++14: error (deleted copy constructor) / C++17: copy elision(!!)
A b = static_cast<const A>(A{});
}
This does not compile in C++ <=14, but compiles in C++ >=17. In a common case where a class is movable but not copyable, const
meant there were no means to get a non-const object out of it before C++14, but it doesn't anymore (as long as const
is added to a prvalue).