Recently, I'm writing a reference-like proxy class. I'm trying to make this class really reference-like as much as possible, though I'm still discovering a new situation where it can't (e.g., bad interaction with auto, lifetime extension, and others). It seems that it is just impossible to make a perfectly reference-like proxy class in the current specification of the language.
Anyway, I'm still trying to make my class looks like a real reference for most of the situations. One thing that confuses me right now is const-correctness. It seems to be a quite established advice to make "reference" and "reference-to-const" separate classes that are connected together by some conversion relations. But what should I do for "const reference"?
It seems relatively obvious that there should be no difference between "reference-to-const" proxy and "const reference-to-const" proxy. But what about mutable reference?
(1) Should a "const reference" behave like "reference-to-const", or
(2) There should be no difference between const and non-const proxies?
I've thought that also in this case there should be no difference between const and non-const proxies because this constness applies to the proxy itself, not to the "referred object". However, it seems that some well-known examples of proxy classes (std::vector<bool>::reference
and boost::multi_array_ref
) do not act like that; const proxies behave somewhat like reference-to-const. It seems they can be classified as the case (1).
I'm now increasingly believing that (1) should be the right choice, but I'm not quite convinced because I've believed "reference" in C++ is just a syntactic sugar for representing non-nullable const pointer. Because a reference is "already const", there should be no difference between const and non-const qualified proxies. As a supporting evidence for (2), the following code is allowed:
struct A {
int& x;
};
int main() {
int x;
A const a{ x };
a.x = 10; // now, x above becomes 10
}
Here, constness of a
does not applies to (the object referred by) a.x
.
"No difference between const and non-const qualification" implies, I think, EVERY member function of the proxy class should be qualified as const, INCLUDING copy/move assignment operators. For example, if we choose to follow (2), the copy assignment operator should look like
Proxy const& Proxy::operator=(Proxy const& that) const/*?!*/ {
// assign referred-to-objects
return *this;
}
and that const
looks really unfamiliar. Also, if we choose to follow (2), there may be a problem like the following:
ProxyReturningContainer c;
for (auto const& x : c) // may expect reference-to-const, but gets mutable reference
{
// do something with x
// fortunately (?), x is not a dangling reference due to lifetime extension
}
These may be regarded as supporting evidences for (1).
P.S. As a (possibly not) related topic, I've figured out that it seems very natural to treat ref-qualifier of a proxy object as something that acts like the reference collapsing rule. That is, if my proxy is intended to represent lvalue references, then ref-qualifier has no role. However, if my proxy is intended to represent rvalue references, then an lvalue proxy should act like an lvalue reference, while an rvalue proxy should act like an rvalue reference. From this rule, we get the following behavior:
void f(int&& x) {
// inside f, x is now an lvalue; we should write std::move(x) in order to get rvalues
}