$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
From: Fernando Cacciola \(Home\) (fernando_cacciola_at_[hidden])
Date: 2003-02-14 09:36:49
Aleksey Gurtovoy wrote:
> Fernando Cacciola wrote:
>> OK, I can see the motivation: We can have a noncopyable class
>> and need an optional object of it.
>> Following optional semantics, it would be spelled:
>>
>> boost::optional<RAII_lock> lock;
>> if ( cond )
>> lock.reset( RAII_lock(entity) ) ;
>>
>> But there is a probem: as William pointed out, reset() needs
>> to use T::T(T const&) in order acquire its own copy of the object;
>> but in this case is noncopyable, so the above won't compile.
>
> Yep.
>
> [...]
>
>>> * Secondly, you would need a templated constructor to allow T to be
>>> constructed using another type.
>>>
>> Exactly.
>> One possibility would be add additional templated ctor and reset
>> that just forward the arguments to T's contructor (optional's interface
>> could use a trailing tag to differentiate these).
>>
>> A problem with this is that the Argument Forwarding problem is burried
>> into optional's itself.
>
> I think it's more of a language problem than anything else. I mean, there
is
>
> nothing conceptually wrong with allowing 'optional' to support something
> like 'opt.construct(a1,...,an)', IMO; only implementation ugliness.
>
Agreed.
Though the problem still exist in practice. I'll get back to this below...
>
>
>> Another approach would be to create a generic delay-factory
>> object and just let optional use it:
>>
>> Something like:
>>
>> template<class T, class A0>
>> struct in_place_factory0
>> {
>> in_place_factory0 ( A0& a0_ ) : a0(a0_) {}
>>
>> T* operator() ( void* address ) const { return new
>> (address) T(a0) ; }
>>
>> A0& a0 ;
>> } ;
>>
>> template<class T, class A0>
>> in_place_factory0<T,A0> in_place ( A0& a0 ) { return
>> in_place_factory0<T,A0>(a0) ; }
>>
>> which requires the following changes in optional<>:
>>
>> template<class T>
>> class optional
>> {
>> public :
>>
>> ....
>>
>> template<class Factory>
>> explicit optional ( Factory f )
>> :
>> m_initialized(false)
>> {
>> construct_inplace(f);
>> }
>>
>> ...
>>
>> private :
>>
>> template<class Factory>
>> void construct_inplace ( Factory f )
>> {
>> f(m_storage.address());
>> m_initialized = true ;
>> }
>> } ;
>>
>> The above would allow something like this:
>>
>> optional<RAII_lock> l( in_place<RAII_lock>(entity) );
>>
>> If the same in-place idiom is used on reset(), the complete
>> solution will look, following optional<>'s way:
>>
>> boost::optional<RAII_lock> lock;
>> if ( cond )
>> lock.reset( in_place(entity) );
>>
>> What do you think?
>
> Well, it's a little too verbose and un-idiomatic than I would hope for.
I see.
>
> May be my original desire to re-use 'optional' in this context was
> ill-conceived, and what I really need is a separate 'optional_lock'
class -
> which, although definitely similar, will be significantly simpler than
> 'optional' itself. Although
>
> boost::optional<scoped_lock> lock(cond, entity);
>
> definitely seemed so intuitive and close.
>
> Aleksey
>
Well, even if a separate class is better for your purposes,
you've raised a very good point, and one way or another
I'd like to support this usage in optional<>.
Thus, I'd like to further discuss the posible interfaces
(others really welcome).
I started considering the interface you suggested and even implemented
and played with it, then I spoted the following issues:
(1) I feared that the syntax could look ambiguous:
struct X : noncopyable { X ( bool, int, char const*) ; } ;
struct Y { Y ( bool ) ; } ;
void foo()
{
optional<X> opt1(true,false,2,"hello"); // [1]
optional<Y> opt2(true); // [2]
}
In [1], in-place construction is used, so the first parameter is the
condition and the other three are the contained object's parameters.
In [2], in-place construction is not used and instead a temporary Y is
created
and stored.
I initially thought that the in-place form would have to always take more
than one parameter: the condition and the in-place ctor argument.
If this were true, one could tell one form from the other unambiguously
(and this is important because in the in-place form the first parameter
is related to the initialization state of the optional instead of the state
of the contained object).
But it isn't: what about a non-copyable object with just a default ctor?
struct Z : noncopyable { Z() ; } ;
void bar()
{
optional<Z> opt1(true); // in-place form
optional<Y> opt2(true); // copy form
}
Then I thought that a solution could be to have a tag identifing the
in_place form:
void baz()
{
optional<X> opt1(true,false,2,"hello", in_place );
optional<Y> opt2(true);
optional<Z> opt1(true,in_place);
}
This way, one can tell clearly when the contained object was being
constructed
in place.
{...then half my neurons died and I felt convinced that the first 'cond'
parameter could be mistaken by a T's ctor argument and decided to remove it
:-o}
But today -back on my feet- I see that the protection is silly (I'm
overprotective, am I not?),
at least with any sort of interface that clearly differentiate the in-place
and the copy forms.
But there was still another issue:
(2) Flexibility
Which forwarding technique shall I use? If the in-place ctor takes arguments
by const reference you can pass const rvalues as in:
optional<X> opt1(true,false,2,"hello",in_place);
but then you can't pass non-const lvalues as in the lock example.
If optional's in-place ctor takes non-const references, you can use
it for cases like the optional lock:
entity e ;
optional<lock> opt(true,e,in_place);
but then you can't pass literals as before.
Additionally, how many forwarded arguments shall optional support?
... Murphy will make sure a new user will request support for yet another
argument
(though this could be solved using BOOST_PP and a user-defined limit)
(3) variant<> dependency.
We are just about to review boost::variant<>.
I was planning to wrap optional<> around variant discarding its current
implementation.
However, to support the in-place form and use variant<>, the later will have
to suppport this form itself.
Proposed solution:
I realized that from the user POV, there is little syntactical difference
between:
optional<lock> opt(true,e,in_place);
and
optional<lock> opt(true,in_place(e));
so I figured that a factory could move the forwarding problem out of
optional<>
while at the same time allow for an almost identical usage.
{...actually, I've dropped the 'cond' parameter in my original thoughts and
post,
but it was unnecesary...}
This form is just a little more verbose than the direct form
(optional<lock> opt(true,e)), but has the following nice features:
It allows to use in-place for noncopyable default-constructible objects:
optional<Z> opt(true,in_place());
It allows you to have alternative factories which support alternative
forwarding methods:
optional<X> opt(false,in_place2(2,"hello"));
And since the factory can be reused, plugging the mechanism into variant<>,
for instance, is trivial.
Notice that the verbosity is actually put in the in-place factory mainly.
I would implement and document such factory as another utility and just
use it on optional<> interface.
P.S: I'd really like to hear others opinions about this,
specially from Eric Friedman and Itay Maman
(the variant submitters).
Fernando Cacciola