
On 7/24/07, Martin Schulz <Martin.Schulz@synopsys.com> wrote:
I tend to disagree. I think of an proxy object not so much as a "reference" but rather as a "drop-in replacement" that happens by chance to be connected to some other container object, but that is only an implementation detail.
If the proxy models an STL container then it should work with any STL or
other appropriate algorithm and I do not consider this a hack. It simply works as designed.
If it were possible, it would be useful to be able to think about proxies like that, but it misses out on the fact that a proxy to a container in C++ generally is not a model of a standard container, at least not practically, so you generally can't think of a proxy as simply a drop-in replacement, and in practice, because of this, you also can't safely pass a proxy to an algorithm where a container truely is required. You'd do so only with algorithms where a smaller subset of the container requirements are required. As an example of requirements you likely would not want a proxy to meet, consider the constructor and destructor requirements of standard containers (sequences pose even more problems). In order to be a container, copying a proxy would have to imply creating a new container, though the implementation shown above would just produce another proxy which references the same container. This already makes the "drop-in replacement" view not true and is an example of why a proxy as a container itself generally isn't even the desired behavior. Destruction poses another problem, since destruction of a container is required to destroy all of the contained elements. In practice, implementing either requirement is generally not desireable for a proxy. The purpose of a proxy is generally to introduce a partially transparent level of indirection such that you may simulate reference semantics when code is written in places where reference or value semantics may be safely used. So yes, I would say passing a proxy where a container is listed as a requirement would definitely be a hack, unless you can make it truely meet all of the requirements listed above. Usually you can get away with ignoring this fact and your code will manage to compile and work, but consider for a moment a generic algorithm in C++0x which explicitly requires a reference to a standard sequence as an argument. Now the requirement is enforced rather than simply being documented. Here, passing a vector works perfectly fine, as it should, since a vector is a model of a sequence. However, now try to pass a proxy. In order to even have your code compile, the proxy itself would have to be a valid standard sequence, explicitly meeting all of the associated requirements, including the troublesome requirements for proxies mentioned above. Note that this type of issue exists in current C++ code that specifies such requirements in documentation, only the language isn't going to catch your logical error for you, and instead you will just be violating your own contracts. If you make the requirements for the algorithm less strict such that both a proxy and a container itself meet the requirements, then all is fine and correct, given that you interface with the stored data in a way which works with objects and with proxies, but if you instead lie in code and state that the proxy is a valid sequence when it is not, knowing that internally the algorithm doesn't actually need to rely on all of the specified requirements, it is simply a hack to get around a design flaw. That's not to say that proxies aren't useful, it just means that in general, they are not true drop-in replacements that go anywhere the target type would go, unlike what many people tend to believe, and if your proxy doesn't correctly model the concepts that your referenced object models, then you have made a logical error if you attempt to use it where those requirements are specified, whether your code seems to work or not. Because of this, you should try to provide as much functionality as you can when implementing the proxy, but you can't ignore the fact that a level of indirection still exists. Therefore I favor option 1).
Clearly a non-const proxy may not be constructed from a const container.
When I pass some container-alike object into some method e.g. as const
reference, I do not expect its contents to be modifyable.
As was pointed out by Peter and Eric, when copying proxies for example, this type of logic can't even be enforced, and I argue that this is because at a higher level, your proxy is still logically dealing with a level of indirection, yet you are incorrectly attempting to combine the cv-qualification of the target with the cv-qualification of the proxy, despite the fact that they are two logically different concepts, only somewhat blurred by the fact that much of the interfacing is automatically forwarded. You can try to force certain meaning out of the top-level qualification that "just works" for many, though not all, algorithms, depending on how they are written, but then things start to get hairy since you are essentially altering the meaning of top-level cv-qualification such that it propogates through and is inseparable from the qualification of the target type. Unless your proxy manages to implement true value semantics, I don't see how you can logically allow the qualification to fall through.
members and propagate the constness of the member function to the
(1) typically only makes sense for types that are intended to be class proxied
(referred to) object for some reason.
Right, that would seem to be a legitimate use.
I agree that if you wanted such functionality this would work fine, however, I would personally like to see somewhere in practice that this actually is the desired behavior and where what you are dealing with truely is a proxy -- explicitly noting the requirements of the algorithm or datastructure. I don't doubt that it is possible that such cases may potentially exist, though I am not convinced, and offhand, none jump immediately to mind. -- -Matt Calabrese