Boost logo

Boost :

From: Corwin Joy (cjoy_at_[hidden])
Date: 2001-07-14 15:51:47


----- Original Message -----
From: "Kevlin Henney" <kevlin_at_[hidden]>
To: "Corwin Joy" <cjoy_at_[hidden]>
Cc: <boost_at_[hidden]>
Sent: Saturday, July 14, 2001 5:50 AM
Subject: [boost] Re: "any" suggestions

> In message <003001c10c2c$7faf3e10$2101bf0a_at_[hidden]>, Corwin Joy
> <cjoy_at_[hidden]> writes
> >Proposal 1: A second "any" class which I will call anyx (for "any"
> >eXtractable).
>
> I think a different name would be appropriate, as many will mistake this
> for the uninformative but common "x" as "extended" naming convention.
>

Sure. The point is to have a seperate class from the basic "any" class since
it provides extra facilities but also places additional requirements on the
held object - e.g. that it be streamable.

> >This is the same as the any class but with stream operators
> >i.e.
> >
> > class anyx
> > {
> > public:
> > friend std::ostream & operator <<(std::ostream &o, const anyx &x)
> > friend std::istream & operator >>(std::istream &i, const anyx
&x);
>
> That these are friend operators is an implementation assumption. Also, I
> think you meant to omit the const on the any reference for stream
> extraction.

Yes.

>
> >...skip the obvious implementation with virtual functions etc...
>
> Actually, I don't think this is something that you can skip. What is the
> "obvious" implementation? Output is trivial, but what do you intend for
> input? Are you proposing a full-blown factory mechanism that can somehow
> identify the type on the stream or are you assuming that input for an
> any will be based on its current type?

I am proposing that the input be based on its current type.

> This latter suggestion would
> generally be more frustrating to users than it would be useful, eg
>
> boost::any value;
> std::cin >> value;
>
> would fail because value has no current type.

This is not a problem with the stream operator. Rather this is a problem
with the "any" class. Frankly it's quite goofy the way this class default
constructs with an artificial void/empty type. Better would be to not allow
default construction of an "any" object than to have this artificial empty
type. Anyway, if one stays with the flawed way that "any" currently default
constructs I don't find it at all unreasonable for the stream operator to
fail / throw in the case of streaming into an empty type.

> And the following would
> rather defeat the object of supporting I/O in the first place:
>
> boost::any value = 0.0;
> std::cin >> value; // will attempt to extract a double
>

This does not at all defeat the purpose of supporting I/O. There any many
times when you want to use "any" as a kind of prototype design where where
you say that the type must is set by the initial construction and any
further "writes" to that type must be cast into that type. Here the
semantics of this istream operator mean "read from the stream into the
format of the currently held type". If you want to read from the stream and
simultaneously try to "guess" the type, that is a different animal in my
mind and would likely be better done as some type of constructor.

> >There are two reasons why this would be nice:
> >1. You often want to send a variant type to a stream or output.
> >2. If you really want a cast operator from any you can now get it via:
> >
> >any a("3.1415");
> >double d = lexical_cast<double>(a);
>
> If this kind of code is common, eg moving between text and numbers, then
> any is the wrong solution: string and lexical_cast provide a more
> appropriate approach.

It is true this is an inefficient solution if the op is common. My point is
that "any" is rather useless without providing some kind of operation to
safely cast out values to types other than just the currently held type.
The current approach of any_cast which says, "well, we only allow you to get
back out the value in the form of the original type" is ridiculous. If you
know that the type is fixed at compile time, why would you use "any" in the
first place. I see "any" as primarily useful for situations where you don't
know the type until rutime which means you are going to need to do type
inquiries or casts on the held value. The reason is, if you are holding an
object as part of you program odds are you want to do something with it
other than just hold it in memory and copy it around. Most likey you are
going to want to apply operations to the object such as, e.g.:
stream to output
add 1
subtract an amount
concatenate a string
etc.
The current approach of any_cast just punts. Effectively to use "any" as it
stands the client ends up with a "guess the type" game every time they want
to use the value
e.g. function to add 1 to an "any" variable :
// find out if it is a numeric type to see if 1 can be added
// long chain of either any_cast calls or switch case on the type_info
// just to find if we are numeric
any_cast<double *>
any_cast<int *>

Basically the "any" class ends up transferring all the type work to the
client which makes the class not very usefull in the end. IMO a good
generic class supports some runtime type facilities that allow you to
perform safe casts from one type to another - or throw if such a cast is not
allowed. "any" class which doesn't support a good runtime class facility is
simply going to transfer this work to the user where it will typically be
done in an unsafe manner.

>
> >Also, this lexical cast is pretty inefficient if you want to cast across
> >pure numeric types. It would be nice to have the equivalent of
numeric_cast
> >working with any. Surprisingly, I think this can also be done.
>
> You're right, I'm surprised ;-) Surprised at both the alleged
> inefficiency -- inefficient wrt what exactly?

I was referring to the inefficiency of heavily using stream methods to try
to cast across types (plus, this is not very safe).

>> -- and surprised that it
> could be done in an effective manner without changing the basic intent
> of any: values are held but not interpreted.

Which comes back to our fundamental difference. I think there need to be
facilities for runtime casts in a safe manner. You, I guess, do not.
>
< ..snip... discussions of how an "any" numeric cast facility might be
written -- doesn't seem to be much point in having this discussion if there
is not agreement that a numeric cast facility is usefull in the first
place>

> >Suggestion 2:
> >The current "any" is quite inefficient in that both the clone and the
> >constructor allocate with just a simple new. If you are using a lot of
> >"any" instances this is going to thrash the allocater & be quite
> >inefficient. I think it would be very nice to take advantage of the
boost
> >pooled memory allocator here.
>
> Have you demonstrated this thrashing? I haven't explored this in detail,
> but for many uses of any you seem to get good recycling.
>
I haven't done numerical tests -- mostly because this point strikes me as
quite obvious. Consider what happens if you have e.g. a vector of 1000
"any" objects. If you copy this vector you are going to get 1000 calls to
clone() which calls new() 1000 times to allocate a whole bunch of typically
tiny objects (int, double, long, most likely other small & commonly used
types). This kind of thing is well known to be quite inefficient - rather
than going into a whole long redundant explanation I encourage you to read
the boost "pool"docs or some of the SGI STL docs on pool allocators. Even
worse, some heap allocators such as the one that came with VC5 would
actually crash the system after too many small allocations and deallocations
so not thrashing the memory with a whole bunch of tiny "new" and "delete"
calls becomes quite important. Even if you don't agree with the pool
implementation in boost, I think it would be quite good to upgrade "any" to
take an allocator so that end-users who don't want their heap trashed can
control the allocation behaviour.

Corwin
____________________________________________________________


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