Solutions 4 and 5 on GotW #6 Const-Correctness mention this:
Point GetPoint( const int i ) {
return points_[i];
}
Return-by-value should normally be const for non-builtin return types ..
int GetNumPoints() {
return points_.size();
}
.. since the int is already an rvalue and to put in 'const' can interfere with template instantiation and is confusing, misleading, and probably fattening.
I have the following questions
- Which template instantiation are we interfering with here ?!
- What exactly is misleading here and why?
- Why this differentiation between non-builtins and builtins. I thought this was a bad practice!
Return-by-value should normally be const for non-builtin return types ..
That is not correct- one of the relatively few mistakes in the GotW. const
rvalues were questionable in C++03 and definitely very bad in C++11.
The problem is that in C++03, rvalues can have any member function called on them- even non-const. This is great because you can "swaptimize" or perform method chains and other things, which are perfectly fine and make absolute sense, but it's also bad because the compiler can't catch you when you do something stupid, like assign to it or something. Generally, it's a bad idea to restrict everyone from doing good things because the caller might do something stupid. The intention is good, but it's not right.
In C++11 this is fixed because you can disallow member functions from being called on rvalues, and secondly, because in order for move semantics to work correctly the rvalue must be mutable. You can't take resources from an rvalue if it's const
.
Just as a note, the reason this is different is because primitive types have always had special wording built into the language which made e.g. assigning to rvalues illegal, so there was no need to try and enforce that yourself by making it const
.
As for the template instantiations, I'm actually uncertain. This practice was already known to be bad by the time I started coding C++, so it's not something I ever had to deal with.
Personally, I disagree with the recommendation to put const
on the return value independent of move semantics: the value is already an rvalue and as a result there isn't much danger of accidentally modifying it. This is the part of why to put a const
on non-built-in types: they may contain some sort of a backdoor to the value. For example, std::vector<T>
has a swap()
method which can be used to "steal" the content of a non-const rvalue:
std::vector<int> f();
std::vector<int> value;
f().swap(value);
Similarly, streams have some member operators which allow you to use them with certain built-in functions which effectively extract a reference from the stream, e.g.:
std::string word;
std::istringstream("hello, world") >> std::skipws >> word;
Without the std::skipws
the stream is an rvalue which can't be bound to the first argument of std::operator>> (std::istream&, std::string&)
but using the member operator for manipulators returns a non-const reference to the stream.
The const
on build-in types actually has no effect at all. In particular, when passing the result of a function on to a function template (in C++2003) it can't distinguish between a const
or a non-const
return being passed on. As a result, it may appear as if the const
has an effect on built-in returns although it actually doesn't.
As I said, I disagree with the rule and in C++2011 it definitely doesn't hold because you want to be able to move off a non-built-in anyway which would be prevented by the const
return.