Boost logo

Boost :

From: David Bergman (David.Bergman_at_[hidden])
Date: 2006-07-25 12:37:12

Beman wrote:

> While integers are often used as the underlying type, any
> type that meets a few common requirements (CopyConstructible,
> etc.) can be used. One of my test cases uses std::string.

Yes, but if I wanted to embed the type std::string with my specialization,
for some special handling (perhaps to create a Pascal string as its
conversion to "char*"? ;-) ), the term (and idea of) 'identifier' does not
make much sense. I.e., I felt your proposal - with some modifications and
extensions - to be applicable in a far wider scope.
> I'm on a road-trip so may not get a chance to look at your
> stuff for a few days. One thing I'll look at then is to
> compare several common uses of identifier against your
> proposal to see if the added generality gets in the way of
> simple use cases.

The identifier notion could be implemented as

        typedef mpl::vector<
                policy<embed_order>, // propagate order
                policy<embed_equivalence>, // propagate equivalence
                policy<embed_assignability> // propagate assign-from-raw
> ident_policies;

        template <class T, class D>
        struct identifier : embed_type<T, D, ident_policies> {
                identifier(T t) : embed_type<T, D, ident_policies>(t) {};

after which 'identifier' should carry your semantics, except for stream
operators which are not currently implemented in the embed_type proposal and
your clever use of 'unspecified_bool_...'. Ah, I see that you have a boolean
negation operator. That could be a useful preservation, so let's define a
policy for it:

        template <T, D, Base>
        struct embed_neg : Base {
                bool operator!() const { return !this->value_ref(); }

and then add it, as

        template <T, D>
        struct identifier2 : embed_type<T, D,
                mpl::push_back<ident_policies, policy<embed_neg> >::type> {
                identifier2(T t) : embed_type<....>(t) {}

One could also possibly use SFINAE to bypass policies (read "homomorphisms")
not making any sense for the specific embedded type, such as trying to add
policy<embed_arithmetics> to struct Foo { void foo(); }; I will investigate
that extension.

> The other question is how important/useful
> are the other possible uses of your proposal that go beyond
> identifier.

I see a lot of proxy/wrapper uses, where most aspects/structures are
preserved, but some are not. The policy system also allows for
concept-specific homomorphisms to be implemented, building up a portfolio,
and then used in concrete cases.

An example of a float wrapper where we do NOT want to propagate
        struct almost_float : embed_type<float, almost_float,
                mpl::remove<embed_policy_all, policy<embed_arithmetics> > >
                almost_float(float f) : embed_type<....>(t) {}

One could also not only chose not to propagate a behavior but to replace it,
such this policy restricting arithmetics to positive values, i.e., enforcing
a non-group (algebraically) behavior:

        template<class T, class D, class Base>
        struct pos_arith_policy : Base {
                operator D operator+(const D& rhs) const {
                        return D(this->value_ref() + rhs.value_ref());
                operator D operator-(const D& rhs) const {
                        if (this->value_ref() < rhs.value_ref())
                                throw std::runtime_error("Oops, we are not
in a group anymore, so be careful!");
                        return D(this->value_ref() - rhs.value_ref());

which can be used to define a special policy list replacing ordinary
arithmetics with the checked variant:

        typedef mpl::vector<
policy<embed_arithmetics> >,
                        policy<pos_arith_policy> >

With this policy list, we can create positive variants of any arithmetic
type, such as:

        struct positive_float : embed_type<float, positive_float,
positive_arithmetics_policies> {
                positive_float(float f) : embed_type<....>(f) {
                        // Make sure we start out with a non-negative float
as well...
                        if (f < 0)
                                throw std::runtime_error("If you want
positive, please do not start out negative!");

We can now use this positive_float as any float, but with the added positive

        positive_float myFloat1(41.0);
        float temp = myFloat1; // fine, since we inject embed_conversion
        positive_float myFloat2 = myFloat1 + 1.0; // fine
        positive_float myFloat3 = myFloat2 - 43.0; // Oops, will trow!

One cannot only extend a type with checks, such as these, but also distort
behavior, such as with this flipped arithmetics policy, of whose usability
one can discuss; perhaps in a stack implementation of some form, or for
transparent implementation of a reverse iterator?:

        template<class T, class D, class Base>
        struct flip_arith_policy : Base {
                D operator+(const D& rhs) const { return D(this->value_ref()
- this->value_ref()); }
                D operator-(const D& rhs) const { return D(this->value_ref()
+ this->value_ref()); }

Let's use it:

        struct flipped_int : embed_type<int, flipped_int,
policy<flip_arith_policy> > > {
                flipped_int(int n) : embed_type<...>(n) {}

        flipped_int myInt1 = 43;
        flipped_int myInt2 = myInt + 1;
        std::cout << "43 + 1 = " << myInt2 << std::endl;

Yes, you guessed it: 42!

I hope this triggered some use cases, even though, admittedly, my examples
here are pretty lame.


Boost list run by bdawes at, gregod at, cpdaniel at, john at