Re: [boost] New Library Proposal: dual_state

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Rob Stewart Sent: Thursday, July 14, 2005 3:34 AM To: boost@lists.boost.org Cc: boost@lists.boost.org Subject: Re: [boost] New Library Proposal: dual_state
From: "Jost, Andrew" <Andrew_Jost@mentor.com>
From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Fernando Cacciola
Here is a real example. Imagine a researcher with many instruments [snip long example indicating that using Boost.Optional for data collection works fine, but for using or reporting that information, Boost.Optional falls short]
Okay. This works, but what about that ugly call to loadPoint? The problem is that the library function is expecting a signal value, not a Boost.Optional! Boost.Optional helped us manage data collection, but does not help us call trend::loadPoint. Let's look at another example.
The library can just as easily expect a Boost.Optional.
But you're assuming that we have control over the library. That's probably the exception rather than the rule in real life. <clip>
translation point is where I see the need for guaranteed object delivery, for it seems that Boost.Optional and guaranteed object delivery are two ends of the same pipeline: at the head, we obtain (or fail to obtain) a value, and in the process escape the need for a signal value; at the tail, we use the values we've obtained, possibly in a context that EXPECTS signal values. Requires them. So far we've used only the trinary operator to perform this translation, but is there a better way?
The fundamental issue seems to be that you want an object or function that automagically produces a value, even if one wasn't otherwise available.
Yes. <clip>
Using adapters, the call to trend::loadPoint in the first example might look like this:
// -- begin // call trend::loadPoint opt_dp::adapter f(-1); for( data_set::iterator p = d.begin(); p != d.end(); ++p ) { tr.loadPoint( f(*p) ); } // -- end
Your version, with Boost.Optional was like this:
for( data_set::iterator p = d.begin(); p != d.end(); ++p ) { tr.loadPoint( *p ? p->get() : -1 ); }
(I think you could change "p->get()" to "**p" if you like, BTW.)
The latter is simpler and seems far more direct and, therefore, readable. I don't see that your adapter has added any value.
I tried to make the examples as syntactically plain as possile; they are optimized for clarity. There is a key advantage in the example with adapters because the adapter is a function object. In that respect, the door has been flung open to many templated constructs that are more difficlt with the ?: operator. For example, a boost::transform_iterator could be created with the adapter to automatically access the underlying objects in a sequence. <snip>
I don't really see the value as presented thus far. There is a tiny amount of syntactic sugar, but is that warranted?
I'm not sure.
But, why do I keep saying optional instead of Boost.Optional? To see, we must think about how an adapter interacts with the optional object on which it operates. I won't spell out the fairly obvious point that adapters should return references rather than whole T objects, but if this is so, then an adapter that operates on an empty optional object must ask that optional to, first, construct a new T object, then return a reference to that new object. Based on this, we can say a few things about an optional object, X, on which an adapter operates:
(a) X must have valid EMPTY and FULL states. (b) There must exist for X a valid EMPTY state, S, in which X privately holds an object, x, of type T. (c) If X is EMPTY (including state S), then altering or replacing the internal x without setting the state to FULL is a "const this" operation.
Your requirements are flawed. The adapter should hold an instance of type T as provided by the constructor. Then, if the Boost.Optional has no value, the adapter can return it's default instance. IOW, the adapter shouldn't impose its needs on the optional type. Boost.Optional would then work fine, though I still question the value of your adapter.
I would also like to see the adapter avoid "imposing its needs" on the optional object, but conversely, I don't think it would be right to to return whole T objects (instead of references).
-- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer; _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

From: "Jost, Andrew" <Andrew_Jost@mentor.com>
From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Rob Stewart
From: "Jost, Andrew" <Andrew_Jost@mentor.com>
From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Fernando Cacciola
Here is a real example. Imagine a researcher with many instruments
Okay. This works, but what about that ugly call to loadPoint? The problem is that the library function is expecting a signal value, not a Boost.Optional! Boost.Optional helped us manage data collection, but does not help us call trend::loadPoint. Let's look at
The library can just as easily expect a Boost.Optional.
But you're assuming that we have control over the library. That's probably the exception rather than the rule in real life.
If you can change the library to use an adaptor for an optional, why can't you change the library to use the optional directly?
Using adapters, the call to trend::loadPoint in the first example might look like this:
// -- begin // call trend::loadPoint opt_dp::adapter f(-1); for( data_set::iterator p = d.begin(); p != d.end(); ++p ) { tr.loadPoint( f(*p) ); } // -- end
Your version, with Boost.Optional was like this:
for( data_set::iterator p = d.begin(); p != d.end(); ++p ) { tr.loadPoint( *p ? p->get() : -1 ); }
(I think you could change "p->get()" to "**p" if you like, BTW.)
The latter is simpler and seems far more direct and, therefore, readable. I don't see that your adapter has added any value.
I tried to make the examples as syntactically plain as possile; they are optimized for clarity. There is a key advantage in the example with adapters because the adapter is a function object. In that respect, the door has been flung open to many templated constructs that are more difficlt with the ?: operator. For example, a boost::transform_iterator could be created with the adapter to automatically access the underlying objects in a sequence.
Hmmm. This reminds me of using the STL algorithms before having Boost.Lambda, Boost.Bind, etc. Without a library of predefined function objects that perform useful, reusable adaptations, or a lambda approach to creating the adapters, one winds up separating the value extraction logic from the context in which it is used.
I don't really see the value as presented thus far. There is a tiny amount of syntactic sugar, but is that warranted?
I'm not sure.
It was your idea, and now you're questioning it? I take from that the responses you've gotten have caused you to rethink not just the approach, but the idea?
(a) X must have valid EMPTY and FULL states. (b) There must exist for X a valid EMPTY state, S, in which X privately holds an object, x, of type T. (c) If X is EMPTY (including state S), then altering or replacing the internal x without setting the state to FULL is a "const this" operation.
Your requirements are flawed. The adapter should hold an instance of type T as provided by the constructor. Then, if the Boost.Optional has no value, the adapter can return it's default instance. IOW, the adapter shouldn't impose its needs on the optional type. Boost.Optional would then work fine, though I still question the value of your adapter.
I would also like to see the adapter avoid "imposing its needs" on the optional object, but conversely, I don't think it would be right to to return whole T objects (instead of references).
There's no need. As I pointed out, the adapter can hold its own T instance which represents the default. Then, when asked for a value (returned by reference), the adapter queries the optional. If the optional has a value, the adapter returns the optional's value. If not, the adapter returns the default value it holds.
-- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer; _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Don't quote things like that. Some like to quote a person's name and put their own immediately after -- for reasons I don't understand -- but there's no need for all of this text. -- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;

Rob Stewart wrote:
From: "Jost, Andrew" <Andrew_Jost@mentor.com>
But you're assuming that we have control over the library. That's probably the exception rather than the rule in real life.
If you can change the library to use an adaptor for an optional, why can't you change the library to use the optional directly?
Adapters work outside the library and don't require any changes to the library. Library requires underlying values, and with the adapter it gets them automagically. The library doesn't know anything about Optional and adapters, library clients do.
There is a key advantage in the example with adapters because the adapter is a function object. In that respect, the door has been flung open to many templated constructs that are more difficlt with the ?: operator. For example, a boost::transform_iterator could be created with the adapter to automatically access the underlying objects in a sequence.
Hmmm. This reminds me of using the STL algorithms before having Boost.Lambda, Boost.Bind, etc. Without a library of predefined function objects that perform useful, reusable adaptations, or a lambda approach to creating the adapters, one winds up separating the value extraction logic from the context in which it is used.
Do you mean that efforts to create a custom function object aren't worth of the result, and most people will just end with using IFs and refusing to create an adapter or a function object for an existing adapter? So far I think we can live with just Optional and OptionalWithDefaultValue. A default value is needed very often. Andrey

Andrey Melnikov <melnikov@simplexsoft.com> writes:
Rob Stewart wrote:
From: "Jost, Andrew" <Andrew_Jost@mentor.com>
But you're assuming that we have control over the library. That's probably the exception rather than the rule in real life.
If you can change the library to use an adaptor for an optional, why can't you change the library to use the optional directly?
Adapters work outside the library and don't require any changes to the library. Library requires underlying values, and with the adapter it gets them automagically. The library doesn't know anything about Optional and adapters, library clients do.
FYI, I might tend to use a dual_state object that either yields a contained _reference_ or the result of invoking a function, because you usually don't want to pay for copy construction of a contained object if you don't have to, and it feels nonuniform to have lightweight copy only when the default is going to be used. -- Dave Abrahams Boost Consulting www.boost-consulting.com

I've been following this dual_state vs optional discussion for a while. It seems to me that one should really have a policy class that defines the default behaviour when the value is unassigned. This policy should define whether to - throw an exception - construct an object on the fly - return a default - return a null pointer - whether to return by value or by reference A second point, can't all this be achieved by adding policies to smart pointers? Also, I can't quite see the benefit of having a dual-state that returns a default value. I mean, why use boost::dual_state<std::string> data; when you can simply use std::string data; ? Calum

"Calum Grant" <calum@visula.org> writes:
I've been following this dual_state vs optional discussion for a while. It seems to me that one should really have a policy class that defines the default behaviour when the value is unassigned. This policy should define whether to
- throw an exception - construct an object on the fly - return a default - return a null pointer - whether to return by value or by reference
A second point, can't all this be achieved by adding policies to smart pointers?
Whoa; smart pointers and boost::optional are not very closely related beasts. If you add enough policies to std::list you could probably handle this use case, too. I don't think finding something to policy-ize is always the best way to fit new designs into an existing framework. -- Dave Abrahams Boost Consulting www.boost-consulting.com

I've been following this dual_state vs optional discussion for a while. It seems to me that one should really have a policy class that defines the default behaviour when the value is unassigned. This policy should define whether to
- throw an exception - construct an object on the fly - return a default - return a null pointer - whether to return by value or by reference
A second point, can't all this be achieved by adding policies to smart pointers?
Whoa; smart pointers and boost::optional are not very closely related beasts.
I mean, a weak_ptr and optional both have 0 or 1 pointees, so there is at least a passing resemblance. In the absence of Boost, I would probably use a std::auto_ptr to store an optional field, which is, um, a smart pointer. I realize that Optional is implemented slightly differently.
If you add enough policies to std::list you could probably handle this use case, too. I don't think finding something to policy-ize is always the best way to fit new designs into an existing framework.
Agreed. Having one uber-pointer that handled everything isn't really the Boost way, and from a practical perspective there's no point reworking all of the pointers. So it was more a "compare and contrast" than a practical suggestion. [As an extreme example, container<std::string, set> data1; container<std::string, vector> data2; is probably silly, but has certain merits. For example you could supply a sensible default containment policy, and it deemphasises the details of the containment mechanism. Of course then the policy does most of the work and we are back to square one, and in this example there is no sensible default containment policy (although vector would be a candidate).] But my original point still stands: since there are alternative behaviours for Optional, a standard pattern in this circumstance is to configure these via a policy, and since C++ templates are zero overhead, it would add flexibility at no cost. The alternatives (e.g. return-by-value) do have run-time overhead. Policies are also good for optimization. I mean if you have an optional int, storing it in-object is fine, but if it is a large object that you need seldomly, an out-of-object storage policy would be more appropriate. But is there sufficient need for different policies in Optional? Perhaps not. If it's not broken don't fix it. Calum

"Calum Grant" <calum@visula.org> writes:
I've been following this dual_state vs optional discussion for a while. It seems to me that one should really have a policy class that defines the default behaviour when the value is unassigned. This policy should define whether to
- throw an exception - construct an object on the fly - return a default - return a null pointer - whether to return by value or by reference
A second point, can't all this be achieved by adding policies to smart pointers?
Whoa; smart pointers and boost::optional are not very closely related beasts.
I mean, a weak_ptr and optional both have 0 or 1 pointees, so there is at least a passing resemblance.
Merely a syntactic one. optional is not a smart pointer; it just uses * and -> to provide access to its contained object.
In the absence of Boost, I would probably use a std::auto_ptr to store an optional field, which is, um, a smart pointer. I realize that Optional is implemented slightly differently.
I wouldn't characterize it as a "slight" difference. -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
"Calum Grant" <calum@visula.org> writes:
In the absence of Boost, I would probably use a std::auto_ptr to store an optional field, which is, um, a smart pointer. I realize that Optional is implemented slightly differently.
I wouldn't characterize it as a "slight" difference.
I hate extra dynamic memory allocation even more than I hate extra copies, because the former cannot be optimized out and usually takes more time than 10 extra copies. Well, pool allocators help a lot sometimes, but it's an optimization technology. I like Boost.Optional because it offers a good way to deal with optonality without any extra pointers, memory allocations, special "NULL" values or construction or copy operations . I think it's way different from the concept of smart pointers. Andrey

Andrey Melnikov wrote:
David Abrahams wrote:
"Calum Grant" <calum@visula.org> writes:
In the absence of Boost, I would probably use a std::auto_ptr to store an optional field, which is, um, a smart pointer. I realize that Optional is implemented slightly differently.
I wouldn't characterize it as a "slight" difference.
I hate extra dynamic memory allocation even more than I hate extra copies, because the former cannot be optimized out and usually takes more time than 10 extra copies. Well, pool allocators help a lot sometimes, but it's an optimization technology.
I like Boost.Optional because it offers a good way to deal with optonality without any extra pointers, memory allocations, special "NULL" values or construction or copy operations . I think it's way different from the concept of smart pointers.
I completely take your point that Boost.Optional would be drastically more efficient in many circumstances. But I would also say it could be less efficient than dynamic memory allocation in a limited number of cases. But we're talking about private implementation. From a semantic perspective, Optional behaves like a smart pointer with a "copy on assign" policy. Perhaps there would be fewer howls of disapproval if I called it a smart container, not a smart pointer. If an implementation improves a smart container/pointer in this circumstance then that's fanstastic. Calum

Calum Grant wrote:
Andrey Melnikov wrote:
I hate extra dynamic memory allocation even more than I hate extra copies, because the former cannot be optimized out and usually takes more time than 10 extra copies. Well, pool allocators help a lot sometimes, but it's an optimization technology.
I like Boost.Optional because it offers a good way to deal with optonality without any extra pointers, memory allocations, special "NULL" values or construction or copy operations . I think it's way different from the concept of smart pointers.
But we're talking about private implementation. From a semantic perspective, Optional behaves like a smart pointer with a "copy on assign" policy. Perhaps there would be fewer howls of disapproval if I called it a smart container, not a smart pointer.
It's just a terminology question. Boost.Optional isn't a "pointer". It isn't an enhanced C pointer, it isn't used like a pointer, it doesn't use pointers internally. It can be "dereferenced", but it's just a syntactic feature. Also it isn't a container in STL sense. It doesn't provides range interface, iterators etc. I would call Boost.Optional a proxy.
If an implementation improves a smart container/pointer in this circumstance then that's fanstastic.
I don't understand you here. Andrey

Andrey Melnikov wrote:
Calum Grant wrote:
Andrey Melnikov wrote:
I hate extra dynamic memory allocation even more than I hate extra copies, because the former cannot be optimized out and
usually takes
more time than 10 extra copies. Well, pool allocators help a lot sometimes, but it's an optimization technology.
I like Boost.Optional because it offers a good way to deal with optonality without any extra pointers, memory allocations, special "NULL" values or construction or copy operations . I think it's way different from the concept of smart pointers.
But we're talking about private implementation. From a semantic perspective, Optional behaves like a smart pointer with a "copy on assign" policy. Perhaps there would be fewer howls of disapproval if I called it a smart container, not a smart pointer.
It's just a terminology question. Boost.Optional isn't a "pointer". It isn't an enhanced C pointer, it isn't used like a pointer, it doesn't use pointers internally. It can be "dereferenced", but it's just a syntactic feature.
Also it isn't a container in STL sense. It doesn't provides range interface, iterators etc.
I would call Boost.Optional a proxy.
Perhaps a broker.
If an implementation improves a smart container/pointer in this circumstance then that's fanstastic.
I don't understand you here.
All I'm saying is that one could regard in-object storage as an optimization over out-of-object storage, which is a "good thing". Calum

"David Abrahams" writes
Whoa; smart pointers and boost::optional are not very closely related beasts.
I mean, a weak_ptr and optional both have 0 or 1 pointees, so there is at least a passing resemblance.
Merely a syntactic one. optional is not a smart pointer; it just uses * and -> to provide access to its contained object.
But also semantically. A smart pointer with a "copy on assignment" policy would behave in almost exactly the same way.
In the absence of Boost, I would probably use a std::auto_ptr to store an optional field, which is, um, a smart pointer. I realize that Optional is implemented slightly differently.
I wouldn't characterize it as a "slight" difference.
Okay, "completely". Just an expression... :-) Calum

David Abrahams wrote:
FYI, I might tend to use a dual_state object that either yields a contained _reference_ or the result of invoking a function, because you usually don't want to pay for copy construction of a contained object if you don't have to, and it feels nonuniform to have lightweight copy only when the default is going to be used.
Well, I had a lot of problems with bottlenecks in VC7.0 code related to redundant copy operations, so I'm very afraid of extra copies. But we can have both copy- and nocopy- versions. The version with a copy operation could be simpler to use, so the both versions have reasons to exist. Andrey

From: Andrey Melnikov <melnikov@simplexsoft.com>
Rob Stewart wrote:
From: "Jost, Andrew" <Andrew_Jost@mentor.com>
But you're assuming that we have control over the library. That's probably the exception rather than the rule in real life.
If you can change the library to use an adaptor for an optional, why can't you change the library to use the optional directly?
Adapters work outside the library and don't require any changes to the library. Library requires underlying values, and with the adapter it gets them automagically. The library doesn't know anything about Optional and adapters, library clients do.
Any function that expects a value can be given either a default or the value in an optional just as easily as an the value from an adapter. However, if the library code retrieves the value itself--which is what I meant by the need to change the library code--then it must know that the type is now an adapter or optional.
There is a key advantage in the example with adapters because the adapter is a function object. In that respect, the door has been flung open to many templated constructs that are more difficlt with the ?: operator. For example, a boost::transform_iterator could be created with the adapter to automatically access the underlying objects in a sequence.
Hmmm. This reminds me of using the STL algorithms before having Boost.Lambda, Boost.Bind, etc. Without a library of predefined function objects that perform useful, reusable adaptations, or a lambda approach to creating the adapters, one winds up separating the value extraction logic from the context in which it is used.
Do you mean that efforts to create a custom function object aren't worth of the result, and most people will just end with using IFs and refusing to create an adapter or a function object for an existing adapter?
Yes.
So far I think we can live with just Optional and OptionalWithDefaultValue. A default value is needed very often.
I'm not sure about OptionalWithDefaultValue. It depends upon how it works and what it imposes on its parameterizing type. -- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;
participants (5)
-
Andrey Melnikov
-
Calum Grant
-
David Abrahams
-
Jost, Andrew
-
Rob Stewart