Boost logo

Boost :

From: William E. Kempf (wekempf_at_[hidden])
Date: 2002-12-09 17:48:56


Fernando Cacciola said:
> "William E. Kempf" <wekempf_at_[hidden]> wrote in message
>> * I believe there should be an "optional& operator=(T const& v)".
>>
> This would break the pointer-semantics:
>
> optional<int> opt ;
>
> opt = 3 ;
>
> instead of
>
> *opt = 3 ;
>
> as it is currently the required syntax.

Things get a little confusing here since optional<> is a smart pointer of
sorts, but it's assigned values not pointers. As long as there's a
constructor taking a value, it seems logical to me there should be an
assignment taking a value. But I won't argue this one too much. But with
out the assignment I think it becomes more imperative to add a reset()
that allows you to modify the value with out the need for a temporary
optional<>.

>> * I think "peek()" should be named "get()". Despite it's unusual (and
>> needed) use, this is a form of a smart pointer, and should follow the
>> same naming conventions that people have grown accustomed to through
>> std::auto_ptr and the Boost smart pointers.
>>
> Fair enough.
> I'm actually trying to vouch for my peek/acquire idiom here.
> Essentially, the name 'peek()' is intended to convey the fact that the
> pointer
> points to a managed object and that the ownership is not being
> transfered along with the pointer (as oposed to acquire()).

Where did acquire() come from?

> If I manage to make the idiom known enough, the user will know that he
> can't delete the pointer and that the pointer can be used only
> as long as the 'source' (the optional<> object in this case) remains
> alive.

OK, that rationale makes some sense, though I don't think it's that
necessary to deviate the names here to indicate the unusual ownership
semantics. I think the class name and purpose do this well enough.

>> * I'm unsure about the need for "value()". I'd be happy with just the
>> normal smart pointer interfaces.
>>
> value() is there just for those contexts in which the conversion
> from the proxy (returned by operator*()) to 'operator T&' is not pick up
> by the compiler.
> For example, when you pass (*opt) to a template function.
> You could use: *peek(opt), but peek() doesn't test for the initialize
> state first,
> as does value().

OK, I'm not understanding something here. In lay terms, what's the
difference between:

o.value() = some_value;

and

*o = some_value;

They seem to serve the same purpose to me, and if so, why does operator*()
need to return a proxy at all?

>> * I think "uninitalize()" should be named "reset()". Not only for
>> consistency with other smart pointers, but also because the term isn't
>> really even meaningful english.
> Hmmm.
> Does 'reset' conveys what it is doing?
> Is it clear that 'reseting' an optional<> means to leave it
> uninitialized? I'm not sure; but if others see it like you do, I'll
> change it.

It makes sense to me. But if reset() isn't understandable, then clear()
or destroy() would be better names.

>> * I'm unsure about the presence of "initialized()". On the one hand,
>> the duplication in features (compared to "get/peek() == 0") is
>> something I think designs should generally avoid. On the other hand,
>> this name is more meaningful for what precisely "get/peek() == 0"
>> signifies. I guess I'm +0 on this one.
>>
> To be honest, I dislike it too :-)
> But some people found the alternative spellings ugly,
> so I figured that a member function would make them happy.

That depends on how much the class follows smart pointer semantics. After
all, std::auto_ptr doesn't contain a null() member, yet no one is confused
by "p.get() == 0". This is another one I won't argue too much, though.

>> * I'm a little uncomfortable with the free functions in
>> <boost/optional.hpp>. With out some rationale for them they seem to
>> provide nothing and are useless duplications in the public interface,
>> which will lead to confusion for users.
>>
> I generally dislike member functions for 'general' objects.
> Onn several contexts, the optional<> objects can be replaced by true
> pointers directly
> thanks to its pointer semantics (and correspoding syntax)
> With the non-member interface, you can write code that works even if you
> change optional<>
> with a true pointer.
> The member function interface is not-recommended.
> It is there only for those situations in which resolution problems
> prevent the user
> from using the non-member functions.

We come from different camps of design, then. I prefer member functions
for the _minimal_ set of operations, and free functions for everything
else. Providing both for the same functionality just lends itself to
confusion on the part of the user (for instance, at the very least they
are left wondering if one is more efficient than the other for some
reason).

>> * I don't follow the rationale for not having relational comparisons.
>> When would you ever get a "maybe" state when comparing?
> Yes, whenever one of the optional<> is uninitialized.

That's not a maybe. That's a definate false. If both were uninitialized...

>>I can see that
>> when comparing two uninitialized optionals that a case could be made
>> for either true or false, but that's not a "maybe" as to my mind you
>> could pick one of them with out causing any ill effects to anyone, and
>> if you do it seems clear to me that you'd want to choose true. I'm
>> not
>> (necessarily) arguing for the comparisons, I'm just questioning the
>> rationale, at least as it's worded in the document now.
>>
> optional<> is intended to be used as much as a pointer as possible;
> therefore
> the syntaxis of its usage is intended to looks like that of a pointer;
> i.e., you can't access optional's "value" directly,
> you need operator*() for that.

That I have no problem with, but that's not the rationale provided!

> Consider:
>
> int* a;
> int* b;
> if ( a == b ) ..
>
> Altough the comparison is well defined between pointers,
> you are not comparing the "values of the objects" being pointed to, only
> the pointer values.
> If you want to compare object values, you must dereference the pointers:
> if ( *a == *b ) ..

Again, this is not the rationale provided in the document. This rationale
I can get behind and support.

> optional<> follows the same idiom.
> You need to use the operator*() to perform the comparisons because you
> need to dereference an optional<> in order to access its value. For this
> reason, the proxy, returned by operator*(),
> does have some conditional operators (== and !=) defined,
> so you can compare optional "values".
>
>
>> * I also don't follow the rationale for leaving out the conversion to
>> bool. I see no conflict with including this conversion, and in fact
>> inclusion would make this more consistent with other smart pointers.
>> There's no confusion/conflict created for boost::smart_ptr<bool>, so I
>> don't see how there could be for optional<bool> either.
>>
> The conversion to bool for a smart pointer unambiguously refers to the
> NULL state of the pointer wrapped.
> In the case of optional, a conversion to bool will be ambiguous in those
> cases were the wrapped T in optional<> is bool itself.

How? You've made this assertion several times, but I don't follow the
rationale. The bool conversion for optional<> would, to me,
"unambiguously refer to the UNINITIALIZED state of the value wrapped", to
borrow your wording with the appropriate changes in terminology. I see no
way in which this can be considered "ambiguous in those case where the
wrapped T in optional<> is bool itself". I.e. these lines would not be
equivalent:

if (o)

and

if (*o)

>> * The fact that optional<T> does not require T to have a default
>> constructor is significant, and should have some more emphasis in the
>> documentation. This is what opens up use cases other than the "return
>> a value or uninitialized" case discussed ad nauseum in the document.
>> For instance, I expect to be able to put this class to use in an
>> implementation of an "async_result" type for Boost.Threads.
> I see.
> Could you give me a summary of the usage in async_result?
> I can add it as the list of usages.
> I know we have talked about this but I don't remember the details.

Well, what I'm envisioning for async_result is different from anything
discussed thus far, but a simple usage example would be:

int foo()
{
   // return some complex and lengthy (execution-time-wise) calculation
}

async_result<int> res;
boost::thread thrd(res.call(&foo));
thrd.join();
cout << *res << endl;

(Note that this is a design in progress and I'd expect several things in
the interface to change slightly, but hopefully this gives you the idea.)

Here, the value "holder" has to be created outside of the call, and passed
in to the function object (though this part is handled transparently),
rather than being created in the function and returned. But it's still
important that we not have to default construct this object, lest you
needlessly restrict the types in which this mechanism can work.

William E. Kempf


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk