Why does operator ++ return a non-const value?
I have read Effective C++ 3rd Edition written by Scott Meyers.
Item 3 of the book, "Use const
whenever possible", says if we want to prevent rvalues from being assigned to function's return value accidentally, the return type should be const
.
For example, the increment开发者_高级运维 function for iterator
:
const iterator iterator::operator++(int) {
...
}
Then, some accidents is prevented.
iterator it;
// error in the following, same as primitive pointer
// I wanted to compare iterators
if (it++ = iterator()) {
...
}
However, iterators such as std::vector::iterator
in GCC don't return const
values.
vector<int> v;
v.begin()++ = v.begin(); // pass compiler check
Are there some reasons for this?
I'm pretty sure that this is because it would play havoc with rvalue references and any sort of decltype
. Even though these features were not in C++03, they have been known to be coming.
More importantly, I don't believe that any Standard function returns const rvalues, it's probably something that wasn't considered until after the Standard was published. In addition, const rvalues are generally not considered to be the Right Thing To Do™. Not all uses of non-const member functions are invalid, and returning const rvalues is blanketly preventing them.
For example,
auto it = ++vec.begin();
is perfectly valid, and indeed, valid semantics, if not exactly desirable. Consider my class that offers method chains.
class ILikeMethodChains {
public:
int i;
ILikeMethodChains& SetSomeInt(int param) {
i = param;
return *this;
}
};
ILikeMethodChains func() { ... }
ILikeMethodChains var = func().SetSomeInt(1);
Should that be disallowed just because maybe, sometimes, we might call a function that doesn't make sense? No, of course not. Or how about "swaptimization"?
std::string func() { return "Hello World!"; }
std::string s;
func().swap(s);
This would be illegal if func()
produced a const expression - but it's perfectly valid and indeed, assuming that std::string
's implementation does not allocate any memory in the default constructor, both fast and legible/readable.
What you should realize is that the C++03 rvalue/lvalue rules frankly just don't make sense. They are, effectively, only part-baked, and the minimum required to disallow some blatant wrongs whilst allowing some possible rights. The C++0x rvalue rules are much saner and much more complete.
If it
is non-const, I expect *(++it)
to give me mutable access to the thing it represents.
However, dereferencing a [edit: no, this is wrong too. I really give up now!]const
iterator yields only non-mutable access to the thing it represents.
This is the only reason I can think of.
As you rightly point out, the following is ill-formed because ++
on a primitive yields an rvalue (which can't be on the LHS):
int* p = 0;
(p++)++;
So there does seem to be something of an inconsistency in the language here.
EDIT: This is not really answering the question as pointed in the comments. I'll just leave the post here in the case it's useful anyhow...
I think this is pretty much a matter of syntax unification towards a better usable interface. When providing such member functions without differentiating the name and letting only the overload resolution mechanism determine the correct version you prevent (or at least try to) the programmer from making const
related worries.
I know this might seem contradictory, in particular given your example. But if you think on most of the use cases it makes sense. Take an STL algorithm like std::equal
. No matter whether your container is constant or not, you can always code something like bool e = std::equal(c.begin(), c.end(), c2.begin())
without having to think on the right version of begin and end.
This is the general approach in the STL. Remember of operator[]
... Having in the mind that the containers are to be used with the algorithms, this is plausible. Although it's also noticeable that in some cases you might still need to define an iterator with a matching version (iterator
or const_iterator
).
Well, this is just what comes up to my mind right now. I'm not sure how convincing it is...
Side note: The correct way to use constant iterators is through the const_iterator
typedef.
精彩评论