|
Boost : |
From: Jost, Andrew (Andrew_Jost_at_[hidden])
Date: 2005-07-10 07:09:38
>From: boost-bounces_at_[hidden]
[mailto:boost-bounces_at_[hidden]] On Behalf Of Eelis van der Weegen
>Jost, Andrew wrote:
>> I am curious if there is support for what I'm calling a "dual_state"
>> template class.
> From your description it sounds a lot like Boost.Optional. What are
the main differences?
>Eelis
I'll admit I did not even pause at Boost.optional when I scanned the
library listing for previous work, a failure in my ability to connect
the description, "Discriminated-union wrapper for optional values," with
the concept I had in mind. I'm not sure if the problem is in my
unfamiliarity with the subject, the description itself, or the concept's
general ineffability. In any case, I'll contrast Boost.optional with
dual_state to see where the differences lie.
But first, let's find a common starting point. The Boost.optional
documentation succinctly states the purpose of that library:
"optional<T> intends to formalize the notion of
initialization/no-initialization allowing a program to test whether an
object has been initialized"
That document also discusses various solutions to the general problem of
handling "signal" values, including cases where the cumbersome pair<T,
bool> construct is necessary because no sensible signal can be defined.
I would say that dual_state is intended to tackle the same set of
problems. However, I see three important differences.
*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
"access to the value of an uninitialized object is **undefined
behaviour**"
The above quote is also from the Boost.optional documentation (emphasis
mine). Boost.optional and dual_state appear to address the same
problems, but with two fundamentally distinct philosophies. Consider a
dual_state<string>. In the following code, the char* in the final
expression is guaranteed to be valid, so the programmer needn't even
bother to check it before calling strlen:
// -- begin
typedef dual_state<std::string> state_string;
state_string str;
int len;
...
len = strlen( str->c_str() ); // always a valid char*
// -- end
Note that operator-> is used to access a base class' member. The
analogous code using Boost.optional is as follows:
// -- begin
typedef boost::optional<std::string> opt_string;
opt_string str;
int len;
len = strlen( str.get().c_str() ); // UNDEFINED!
// the correct way
if( !str ) {
// do something else <== HERE
} else {
len = strlen( str.get().c_str() ); // okay now
}
// -- end
A programmer may view dual_state's "guaranteed delivery" behavior as
either advantage or liability, but his opinion, if tenable, must focus
on the relative benefits afforded by guaranteed objects versus the risks
posed by -- one might say -- "junk" default values. I would say it
depends on the application. More to the point, if the answer to what
goes "<== HERE" is always "set len = 0" for a particular application,
then dual_state could offer greater clarity with less work.
*FULL OPERATOR SUPPORT*
A second difference is that dual_state directly supports the full
complement of operators (for built-in types), this due to the fact that
most operators are delegated through the implicit
conversion-to-base-type operator. Exceptions to implicit delegation are
those operators that modify the dual_state object itself. The example
below demonstrates two operators, + and +=. Addition operates via the
conversion to T (not T&) operator and therefore returns a regular T
object, at the cost of one extra copy (a copy, incidentally, that can be
avoided by using the value() member). The concerted += operator, which
is defined by dual_state, avoids copies and evaluates to a dual_state
reference:
// -- begin
typedef dual_state<int> state_int;
state_int x;
x + 5; // evaluates to (int); x still undefined
x += 5; // evaluates to (state_int&); x is defined, equal to 5
x.value() + 5; // also evaluates to (int)
typedef boost::optional<int> opt_int;
opt_int y;
y + 5; // won't compile
y += 5; // nor will this
y.get() + 5; // evaluates to (int)
// -- end
Again, the question of which behavior is most desirable hinges on the
programmer's needs. While Boost.optional and dual_state both give the
programmer a way manage defined and undefined objects; a way to manage
initialized and uninitialized data; a way to sidestep "signal" values:
EOF, -1, n_pos -- the semantics of one approach or the other may be
better suited for a given application.
*THE UNDEFINED_OBJECT*
A third difference between Boost.optional and dual_state involves the
very personal issue of syntax. It is nonsense to tell people to prefer
one syntax over another, but I will note that dual_state's use of the
undefined_object lends a unique feel to code that uses it. The
programmer's intent is unmistakably clear when "undef" is used to
initialize members:
// -- begin
using dual_state_namespace::constants::undef;
class bar { ... };
class foo {
public:
dual_state<int> x, y, z;
dual_state<bar> a;
foo() : a(undef), x(undef), y(undef), z(undef) {}
};
// -- end
The concept of using self-evident constructs to obviate documentation is
a valuable one. It is detailed in the Boost.noncopyable notes.
*SUMMARY*
My intent has been to clearly and accurately articulate the conceptual
differences I see between Boost.optional, a reviewed and tested library
having several years usage history, and dual_state, a concept. In that
I have succeeded or failed, others may comment, judge.
-- Andrew M. Jost
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk