
On Fri, Dec 19, 2008 at 18:16, Vladimir Batov <batov@people.net.au> 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@people.net.au> 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