Boost logo

Boost :

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


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