Boost logo

Boost :

Subject: Re: [boost] [uuid] Interface
From: Scott McMurray (me22.ca+boost_at_[hidden])
Date: 2008-12-20 17:37:49


On Fri, Dec 19, 2008 at 18:16, Vladimir Batov <batov_at_[hidden]> wrote:
>>
>> What defines "fully usable"? All the member functions in a nil UUID
>> return reasonable, predictable results, and none throw exceptions.
>
> Surely "predictable results" do not make a thing usable. A broken-down car behaves predictably (does not start). It even might be considered "usable" (as scrap metal or a frontyard "feature"). It is not usable from the *conventional* mainstream point of view as it fails to provide the service the car was *designed for* in the first place.
>
> My interpretation of "fully usable" is a "valid" object, that is an object that provides the *main* functionality the class's been designed/introduced for. For a pointer it'd be a pointer pointing somewhere, for an integer, double, std::string, std::map, etc. it'd be *any* integer, double, std::string, std::map as all their values are valid (those classes just have no an "invalid" concept), i.e. int() is valid, std::string is empty but is still valid.
>

What makes the Nil UUID not "valid"? It has its own section in the
RFC, and regardless of what the default constructor does, there's
going to be uuids::nil() to get an instance of it. If it's "invalid",
why is it still available?

>> Do you have some examples of other objects that do (or should) behave
>> similarly to your desire for uuid?
>
> int example = int();
> double d = double();
> std::string str;
> in fact probably all the classes with the value semantics.
>

None of those do any generation of any sort; They all have T() == T();
And they all are rarely useful as-is.

>> In my view, a uuid is essentially
>> a pointer (though the dereference operation is context-dependant), so
>> it should be null/nil by default until you define what it should be
>> pointing to.
>
> To me uuid is a class with the *value* (rather than pointer) behavior/semantics. Value-semantics objects/classes *own* its underlying data. Pointer-semantics objects do *not*.

Does a shared_ptr not own the reference count?

I'd argue that pointers do have value semantics. swap(int *, int *)
can only swap the pointees, not the pointers themselves.

> With pointer-semantics objects the situation is starkly different as a pointer does not house any data. The pointer's *property* is to *point* to and *share* some data. That is its distinct characteristic for which pointers deployed. With that "pointing/sharing" characteristic comes in the twist of *not* pointing anywhere. The notion of a valid pointer pointing to some data is so obvious that it is widely understood that creating a pointer without pointing it anywhere makes it invalid. That is a *distinct* specific characteristic of a pointer. Unfortunately, that interpretation (of default-initialized) is making its way into value-semantics classes for which it is far from obvious/intuitive and in fact in general terms incorrect. Default-initialized is still *initialized*. There is nothing invalid about it.
>

I'd say that the fundamental property of pointers is that, in the
relevant domain, there exists an injective map from set the "pointees"
to the set of pointers (the "address of" operator, for plain pointers)
and a surjective map from the set of pointer to the set of "pointees"
(the "dereference" operator, for plain pointer).

UUIDs fit that definition. If you label each, say, database record
with a UUID, there's a surjective map from the UUID to the record
(with something like "SELECT ... WHERE id = @id" in SQL), and an
injective map from the record to the UUID (by looking at the id field
in the record).

> As for uuid I do not see how it can be treated as a pointer when the pointer's property is to *share* and the uuid's property is to be unique.

No. A uuid object's property is not to be unique. If u == v was
never true when u and v were uuids, they'd be completely useless.

> Surely, we copy a uuid around (i.e. making several copies of the same).

So the uuid objects aren't unique. The "unique" in the name means
that *map* from UUID to resource is Injective, giving a unique
resource in the codomain of the map.

> Surely, internally, those copies can even share the same
implementation (via ref.-counting). Still, those are "technicalities".
For all external purposes uuid is a value-semantics class (and the
discussed class is implemented that way). Therefore, my interpretation
is
>
> uuid id; // Give me the best you (OS/infrastructure) can come up with
> uuid id(some-gen); // I know better. Give me what I am asking for.
> uuid id(uuid::null()); // Do not want anything but have to have this placeholder
>

How is that comment on the latter any different from what the default
constructor for shared_ptr does?

> I bet for portability purposes (and understandable unwillingness to go into every detail) the first will be used overwhelmingly. Some insist that #3 is to be used more often. I disagree as it goes against the C++ grain which states that objects are created when they are needed, i.e. *valid*. To me seeing the following
>
> int i; // Unusable. Initialized later
> int* p; // Unusable. Initialized later
> uuid id; // Unusable. Initialized later
>
> is equally worrisome regardless if an object is "uninitialized invalid" (as int) or "initialized invalid" (as uuid).
>

But what about this?

    int i;
    if (std::cin >> i) ...

You're completely forgetting the other half of using uuids. Yes, you
need to generate UUIDs for each resource, but then someone has to give
you a UUID later. If everything is in the same process, then people
just use pointers, so these UUIDs come from somewhere remote. UUIDs
were invented for RPC.

Take webservices as an example. Yes, you have to generate UUIDs for
the information you make accessible, but it's far more common to have
to read input from someone to get a UUID from them, be that from some
XML stream or from the CHAR field in the database. And the canonical
way to do that in C++ is to use "uuid id; mystream >> id;", just like
lexical_cast does, where the nil constructor (or an uninitialized POD)
is plenty.

>
>> I'm also not the only person that thinks ...
>
> That does not make it right, right? George W. was not alone thinking he was the best candidate. :-)
>
>>> default-constructed instances should be equal.
>
> Well, I find that thought unreasonable and I bet you won't find it in the Standard.
>

I'm saying exactly that that's what I just found, though in the
evolution thereof, rather than its text.

> Take something as reasonable as
>>
>> vector<uuid> v; v.resize(10);
>
> I am not sure what it proves. You seem to extend the notion of the invalid uuid on to a vector of invalid uuids. I suspect "v.reserve(10)" is in order and then you'll put something meaningful into that vector when you have the data. IMHO trying to cater for incorrect usages as above enforces bad programming practices and wrong expectations.
>

My point here has nothing whatsoever to do with what semantics for it
would be best.

The point is that this is a *breaking* change in the standard unless
you assume that value_type() == value_type() for every type that could
be put in a std::vector in C++03. I can't think of any applicable type
that breaks that assumption -- and presumably the committee couldn't
either -- so I don't think that making UUID break it is a good idea.

On Fri, Dec 19, 2008 at 21:55, Vladimir Batov <batov_at_[hidden]> wrote:
> It seems I did it again. :--( Just noticed that I misread the following excerpt of Scott's email. Apologies and corrections further below.
>
>>> Take, for example, a map<string, uuid>. No default constructor means
>>> that you can't use operator[],
>
> First, my preference is to *have* the default constructor -- we disagree on its behavior as I'd like the default constructor to *be part* of the constructors family (constructing a valid object) and you insist on the default constructor being an exception. The latter is not how the language interprets the default constructor. Therefore, me not happy. IMHO that deviation of the meaning from the letter of the language is unacceptible.
>

No, we disagree on the definition of "an exception".

    typedef int *intptr;
    intptr p1 = intptr();
    boost::shared_ptr<int> p2;

Are those "deviations from the meaning" of the language?

>From the previous mail:
>> (I say "trivially"
>> because afaik most uses of uuid do allow you to check whether a uuid
>> points to something or if it's garbage, though not terribly quickly or
>> conveniently.)
>
> You insist on "points". I insist on "has".

A non-nil UUID that doesn't have a corresponding resource is only
slightly less useful than (T*)rand(); The nil UUID is useful, just as
(Y*)0 is.

Perhaps I didn't make what I mean by a UUID "pointing to" something
clear in my previous message; Hopefully I did earlier in this one.
(Not as some actual pointer in the class, but as a sort of "handle",
similarly to how a file handle that has type int doesn't actually
point to whatever struct the OS has associated with it, and to how a
boost::counting_iterator is "dereferenceable" without actually
containing a pointer.)

~ Scott


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