"Augustus Saunders" <[EMAIL PROTECTED]> escribió en el mensaje [EMAIL PROTECTED]">news:[EMAIL PROTECTED]... > > > I like the optional-as-pointer usage,
Great! > and I would prefer it if I > could write generic code where pointers and optionals are completely > interchangeable. However, it isn't really possible to take this all > the way, and we may be luring generic programmers into a trap by > making them think pointers and optionals are interchangeable. > Indeed. optional<> follows pointer semantics precisely because pointers are ordinarily used to convey such 'optionality'. I tried to allow optional<> to be used interchangably with a pointer as much as possible. This is why I insisted on non-member functions and operators. But as you say, the analogy has a limit. Let's explore this limit further: I started saying that optional is not a smart pointer nor a container. Now I think it is rather _both_ of those at partially (but non overlapping) at the same time. It is like a cross-breeded beast; partly smart-pointer and partly (deep-copy) container. I think that this cross-concept is inherent in its purpose and not a design mistake. Let me explain: Optional<> intends to model a possibly-unexistent value, thus (A) It must act like a value-base container at some extent: optionsl<T> opt ; // this part of the interface can be seen as creating the container empty. optional<T> opt(3); // this part of the interface has value semantics: it takes a value as initialization parameter, // and can be seen as 'storing' 3 inside the container. opt.reset(3); // this part of the interface also has value semantics // and can be seen as replacing the previously contained value with the new value 3. opt.reset(); // this part of the interface can be seen as removing the object from the container. These methods give optional<> a deep-copy value-based container-like interface. (B) But it also should act like a pointer at some other extent, because it is extremely convenient to handle the possibly unexistent value through a pointer, since a pointer has a well defined notion of a non-existent pointee: *opt = 3 ; T v = *opt ; opt->foo(); // This part of the interface has pointer semantics. // It is defined only if opt is initialized, which correspond to a non-null pointer pointing to a living object. // As long as the optional is actually initialized, // this code will work exactly the same if opt is replaced by a real pointer, which is really convenient. // If the optional is uninitialized, the behaviour of this code is undefined, just exactly as it would be undefined if // opt is replaced by a null-pointer. int* p =get(opt); // or get_pointer(opt) to make it consistent with other smart-pointers. // This part of the interface also has pointer semantics. // If the optional is initialized, the returned pointer points to the optional value, which corresponds // to a non-null pointer pointing to a living object. // If the optional is not initialized, the pointer is NULL, which clearly conveys the fact that it is uninitialized. // Regardless of the initialization state, this code will work exactly the same if opt is replaced by a real pointer. if ( opt ) if ( opt == 0 ) if ( opt != 0 ) // This part of the interface also has pointer semantics. // Regardless of the initialization state, this code will work exactly the same if opt is replaced by a real pointer. So, Part of the interface has value semantics resembling a container: the ctor and the reset() members. This part of the interace prevents code using _this part_ of optional to be replaced unchanged by a pointer, but, actually, this is OK; since the code to initialize or reset the 'pointee' which is handed through a pointer must (typically) use dynamic-allocation so it must also change beyond the usage of the optional/pointer variable. Only a self-allocating deep-copy smart-pointer would work unchanged, which is also OK. Consider this example that shows this reasoning: optional<int> foo() { optional<int> result (3); // (1) return result ; } void bar() { optional<int> opt = foo(); if ( opt ) use(*opt); } If I want to replace optional with an ordinary smart pointer, I also need to change the line marked with (1): shared_ptr<int> foo() { shared_ptr<int> result ( >>new<< 3 ); // (1) return result ; } void bar() { shared_ptr<int> opt = foo(); if ( opt ) use(*opt); } that is, I need to change the initialization code. But the initiazation is not given by the pointer semantics so it makes sense to me that I need to change it if I want to replace optional by a pointer. The same situation ocurrs with reset(). It does not correspond to pointer semantics thus I need to change the code using reset. In fact, this last observation is what led me to accept William's reset() even though it was a member-function: because assigning a value to an uninitialized optional is an operation that has no correpondence with pointers 'alone': it also requires the allocation of a new pointee besides the assignment. Therefore, being a member, the code will fail to compile if optional<> is replaced by a pointer; showing the programmer that more than just a change of type is needed. Therefore, I conclude that the fact that part of the interface has value instead of pointer semantic is correct, since it is the part of the interface that deals with initialization/deinitialization of the optional value. The rest of the interface deals with accessing the possibly uninitialized optional so it has pointer semantics. It seems to me that both parts of the interface are clearly differentiated both in terms of purpose and in terms of syntaxis, so no confusion should arise. > This is of course because the two optionals never point to the same > object: > > // assume p1, p2 initialized > if (p1 == p2) > { > *p1 = 5; > assert(5 == *p2); // holds true for pointers but not for optionals > } > > Violating this assumption would break code that wants to treat them > interchangeably. > Precisely. Which leads me to the observation that if relational operators would compare values (via the syntax (opt1 == opt2), they would correspond to the value-based part of the interface; but now they won't be clearly cut out: is this working with value based or pointer based semantics? The ambiguity can become a real problem since the code would compile but behave differently. > Some people want to define: > > o1 == o2 iff (o1 && o2 && *o1 == *o2) || (!o1 && !o2) > > whereas (smart)pointers define: > > p1 == p2 iff (p1 && p2 && &*p1 == &*p2) || (!p1 && !p2) > Exactly. (smart or not ) pointers never compare pointee values. > I don't think that it would be possible to code around this point > generically. But is is. Just disallow relational operators between optionals :-) This way, if the programmer wants to compare values he must write: if (*o1 == *o2) which would work exactly the same if optional is replaced by a pointer. And if he wants to compare initialization states he must write: if ( (!o1) == (!o2) ), or if ( get(o1) == get(o2)) which would also work the same unchanged. > Now, having said all of that, I still like the pointer > notation when I understand the lack of equivalence because it is very > concise. > Me too! > Maybe this is just a new concept that needs to be defined > so you can specify it in the requirements to your generic code. Yes I think it is a new concept. > Obviously, there is a lot of generic code that you could write that > doesn't depend on either of the above equivalences--smart pointers > are still useful even though they can't be used as iterators. > Although this is more insidious; it's not the lack of an operator > (++, --) but a redefinition with different semantics. Generic code > that relied on pointer semantics would compile when given an optional > but just wouldn't work. > I believe that if relational operators are banned, generic code given optional will either compile and work as expected, or not compile. In fact, this was one of my first design goals, so if I failed, I'd like to see what can be done to fix it. > > So *I* like optional and would use it like it is, but I'm undecided > whether or not this style interface is just begging for programmer > error. > I think it isn't begging for errors but the oposite. In fact, I _really_ tried to make sure of this... For instance, It didn't had safe_bool just to prevent potential programmer errors! :-) ...though the __wrong__ operator*() escaped me :-( Fernando Cacciola _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost