Re: [boost] New Library Proposal: dual_state

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Fernando Cacciola Sent: Tuesday, July 12, 2005 1:11 AM To: boost@lists.boost.org Subject: Re: [boost] New Library Proposal: dual_state
Now we're getting somewhere. Your post has led me in a new direction so, rather than answer the previous message point by point, I'll just use a few comments as anchors for my explanation. If I leave any question unanswered, please feel free to point it out.
Hi Andrew,
[SNIP]
*GUARANTEED OBJECT DELIVERY* First and foremost, dual_state is guaranteed to always deliver a valid object (or reference), even if this object (or reference) must be conjured from nowhere. This is in direct contrast to Boost.optional, which maintains that
Well, it never ocurred to me to let optional<> provide default values. I just can't think right now of any situation were I would use it. Can you post some *real life* examples? (the example you posted is not good enough because as it is one would argue wthat the optional/dual_state wrapper is not needed to begin with)
Here is a real example. Imagine a researcher with many instruments collecting data. To make it concrete, let's say we have a geologist with dozens of instruments spread across some mountain summit. When he pushes enter on his laptop, all the instruments take a measurement and transmit it to the laptop. Or not. Because sometimes instruments fail, or just fail to respond. Which is why he's using Boost.Optional to store the measurements. So that solves the problem and we can all go home, right? But wait! Once we get off the mountain there is more work to do. Our geologist friend still must retrieve and analyze his data. He will load it into containers and call functions. One of those functions is a member named of the class trend named trend::loadPoint. This member accepts data points into the class then, after all the points are loaded, a call to trend::calculate returns some useful number. To do its work, trend must account for missed measurements, so loadPoint accepts a signal -- namely, any negative value -- to indicate no measurement. For the sake of argument, we'll assume that "trend" is located in a library the geologist cannot change. His code might resemble the following example program: // -- begin #include <boost/optional/optional.hpp> #include <boost/none.hpp> #include <iostream> #include <vector> typedef long double data_point; typedef boost::optional<data_point> opt_dp; typedef std::vector< opt_dp > data_set; // the real "trend" is in a library -- this definition is for illustration only class trend { public: void loadPoint( data_point x ) { if( x<0 ) { cout << "no measurement" << endl; } else { cout << "point: " << x << endl; } } }; int main() { data_set d; trend tr; // make up some data d.push_back(5); d.push_back(boost::none); d.push_back(10); // call loadPoint for( data_set::iterator p = d.begin(); p != d.end(); ++p ) { tr.loadPoint( *p ? p->get() : -1 ); } return 0; } // -- end 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. Say I'm working on a project that persists some configuration data. Momentarily forgetting that Boost.Program_options exists, I decide to use a simple model in which I map string parameter names to string values. Values are allowed to be non-existent, so the map structure might be declared as follows: // -- begin typedef boost::optional<string> param_map_value; typedef std::map<string, param_map_value> param_map; param_map pMap; // -- end After our program runs for a while, and after the user has set a few persistable parameters, it is time to quit, so the program opens a configuration file and begins writing the contents of pMap to that file, which should look like this: // -- begin # CONFIG FILE PARAM_A VALUE_A PARAM_B VALUE_B PARAM_C <undef> ... // -- end In the particular case above, the user has apparently set values for the "A" and "B" parameters, but not for "C". We might write such a file with the following code: // -- begin // assume cf_out is an open ofstream for( param_map::iterator p = pMap.begin(); p != pMap.end(); ++p ) { cf_out << p->first << " " << ( p->second ? p->second.get() : "<undef>" ) << endl; } // -- end This illustrates the same problem as before, namely that it requires us to translate an empty Boost.Optional into something else. This 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?
As David Abraham said, however, IF it turned out to be useful (I can't see it right now) it seems that such an "optional with default" could be a specialized form of optional<>.
Off the top of my head, a default value could be supported in at least 3 ways:
(1) Storing an actual default value in each optional instance.
(2) Parameterizing optional<> with a static factory that can be called to provide such defauts.
(3) Using T's default constructor (if any) to create such a value.
Notice that optional<> does not require T to be DefaultConstructible.
Several people were quick to point out a key conceptual flaw in the original dual_state concept. By delivering only default-constructed objects, dual_state is an arbitrarily circumscribed implementation of a more general, and powerful, model. Suggestions (1) and (2), proposed in the above clip, are a bit better because they let us specify the default value, but a deeper problem remains. It is that by combining the default value with the optional-like storage object, we commit the mortal sin of joining metadata with the real data it describes. It's the same reason we use iterators to serially access containers, rather than absurdly assigning some "current_element" state to the container itself. Returning to the trend::loadPoint example, that function expects the signal value to be a negative number. The next function might expect 0. A third function, yet another signal. The default value is not a property of our data, rather it is a property of the functions that USE our data. In light of this, a better implementation of guaranteed delivery would make use of template function objects that I'll call "adapters". The role of an adapter would be to evaluate a given optional object (I'll refer to anything resembling Boost.Optional as an "optional" object) into either the valid object it contains, or a default value, which is probably defined in the adapter's template specification or constructor. Adapters separate our optional objects from the defaults we might want to use in place of uninitialized T objects. Note that they are incompatible with noncopyable T objects. 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 The second example would look like this: // -- begin // assume cf_out is an open ofstream param_map_value::adapter f("<undef>"); for( param_map::iterator p = pMap.begin(); p != pMap.end(); ++p ) { cf_out << p->first << " " << f(p->second) << endl; } // -- end With a little creativity, programmers could make further use of the STL to craft strikingly elegant solutions. It seems to me adapters would make a useful and appropriate addition to our toolbox, improving the flexibility with which we can access copyable T objects contained in an optional container. 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. As it currently is, Boost.Optional, which we can think of as a stack with fixed capacity 1 and variable size, is incompatible with adapters, but we could certainly define a class derived from Boost.Optional that meets the above requirements. Perhaps, for contrast it could be called "Required"? User's might choose to use Required, sacrificing compatibility with noncopyable objects, in cases where they think adapters will come in handy. This message is already too long, so I'll reserve my additional thoughts until others have a chance to respond. -Andy

Jost, Andrew wrote:
a better implementation of guaranteed delivery would make use of template function objects that I'll call "adapters". The role of an adapter would be to evaluate a given optional object (I'll refer to anything resembling Boost.Optional as an "optional" object) into either the valid object it contains, or a default value, which is probably defined in the adapter's template specification or constructor. Adapters separate our optional objects from the defaults we might want to use in place of uninitialized T objects. Note that they are incompatible with noncopyable T objects.
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
Adapters don't have to require copyable objects. A specialization can deal with noncopyable objects by replacing requirement to underlying type to be copyable with requirement to client code to accept const references and don't require identity semantic. Also, the adapter should live until it's default value is used somewhere. Here is pseudo-code: ------- const T & adapter<T>::adapter(<constructor_parameters>) : default_value_member(<constructor_parameters>) { } const T & adapter<T>::adapt(const T & value_to_adapt) { return value_to_adapt ? value_to_adapt : default_value_member; } ------- I think because this flavour of adapter manages the lifetime of contained default value, it's possible to build a version which would manage a container of multiple "default" objects and would enable identity semantic and remove const requirement. I don't think if this exotic case will be useful though. Also it's possible to decouple the adapter from its managed default value object by using MO managed_object; // accepts, stores and returns a reference adapter<MO> f(managed_object); Also default values don't have to be constants. Default objects can be generated by a generator object (which produces values) or by a factory (which produces identities and may allocate memory). For example, sometimes a random or "next free" default value can be useful. Like in case of bind() TCP socket function with optional port number. And finally, it's possible to relax the requirements to both underlying objects (T) and client code (loadPoint) by storing a reference to an inplace factory inside of Optional objects, but as a price for this improvement we are back to that "metadata binding sin" then. Or we can allow adapters simply to modify underlying objects: adapter<MO> f(<inplace factory for default values>); What do you think about all these proposed adapter designs? Andrey

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.
Say I'm working on a project that persists some configuration data. [snip another example that suggests Boost.Optional is fine for
collecting but not reporting information] > > This illustrates the same problem as before, namely that it requires us > to translate an empty Boost.Optional into something else. This
Yep.
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. [snip recognition that dual_state was ill-conceived due to reliance on default construction]
In light of this, a better implementation of guaranteed delivery would make use of template function objects that I'll call "adapters". The role of an adapter would be to evaluate a given optional object (I'll refer to anything resembling Boost.Optional as an "optional" object) into either the valid object it contains, or a default value, which is probably defined in the adapter's template specification or constructor. Adapters separate our optional objects from the defaults we might want to use in place of uninitialized T objects. Note that they are incompatible with noncopyable T objects.
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.
The second example would look like this:
// -- begin // assume cf_out is an open ofstream param_map_value::adapter f("<undef>"); for( param_map::iterator p = pMap.begin(); p != pMap.end(); ++p ) { cf_out << p->first << " " << f(p->second) << endl; } // -- end
Your version, with Boost.Optional, looked like this:
for( param_map::iterator p = pMap.begin(); p != pMap.end(); ++p ) { cf_out << p->first << " " << ( p->second ? p->second.get() : "<undef>" ) << endl;
That packs a lot into a single statement. I might write it like this: std::string const undef("<undef>"); for (param_map::iterator p(pMap.begin()), end(pMap.end()); p != end; ++p) { std::string const & value(p->second ? *p->second : undef); cf_out << p->first << ' ' << value << std::endl; } Does either version really warrant an "adapter?"
With a little creativity, programmers could make further use of the STL to craft strikingly elegant solutions. It seems to me adapters would make a useful and appropriate addition to our toolbox, improving the flexibility with which we can access copyable T objects contained in an optional container.
I don't really see the value as presented thus far. There is a tiny amount of syntactic sugar, but is that warranted?
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. -- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;

Hi Andrew, It seems we all agree that *alternative* values for optional objects are only needed at the point were such optionals are used, not constructed; so we agree that it is not the direct responsability of optional<> or any other such utility to provide that. Having said that, I do see how the kind of adapter you are proposing can be used to deliver an alternative value given a possibly empty container (the concept could be generalized to operate on containers, not just optional<>). But I'm still largely unsure about the utility of such a thing because it actually hides too much information, like the alternative value which happens to be meaningfull in the local context were it is needed. That is, given: void legacy_consumer( int n = -1 ) ; optional<int> v = get_optional_value(); legacy_consumer( v ? *v : -1 ) ; legacy_consumer( default(v) ) ; The version with the ? is far _better_ IMO than the adapater version becuase it makes it explicit what value is being pass if v is empty (and even that a value is beging pass in such a case). default(v) OTOH is as uninformative as it can be and so I don't think is any better. In one of your posts you mention that you intend the adapter to offer clarity, yet, how can it do that when it hides the default value? I would say that it is "terser", but definitely not more clear. If in the end it all boils down to "value_exist ? value : default" how is a "function object" instead of that a key advantage? You said that "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." I disagree.. can you post a concrete example with a construct that is indeed more difficult with operator ?: (... and be fair; i.e. code the operator version the right way...) IMO you still need to show that the adpator is indeed a good thing; if you do that we can start discussing a proper design and implementation for it. Best Fernando Cacciola SciSoft
participants (4)
-
Andrey Melnikov
-
Fernando Cacciola
-
Jost, Andrew
-
Rob Stewart