Re: [boost] [uuid] Interface

The behavior of the default constructor that you'd like to see (apologies if I got it wrong) is to create an invalid uuid. I feel that is not the canonical use of the default constructor. Indeed, that usage of the default constructor *not* to actually create an object has become quite popular. It does not make it right though. I find it unfortunate and hackish.
I agree. I like things to either default construct with a full usable object, or to not be able to default construct it at all.
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.
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.
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*. Therefore, I feel that the conventional "language" interpretation of Ball ball; is "give me a ball" that satisfies the basic properties of a ball. Then, Ball ball(red); means the same (all the basic ball properties) plus the color. Asking for an unusable ripped into shreds ball is an anomaly, an exception. So, it needs to be treated as such Ball ball(Ball::broken()); so that the majority -- the mainstream users -- do not get confused or pay the penalty of saying "give me a ball, a real God-damn ball and not those shreds". 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. 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. Surely, we copy a uuid around (i.e. making several copies of the same). 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 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).
Take, for example, a map<string, uuid>. No default constructor means that you can't use operator[],
Of course an empty std::map is a valid map. And you certainly can use operator[] with it. :-) But I get your point -- operator[] on an empty std::vector will bomb. It does not make that vector invalid though as a good working car is not "invalidated" by one's attempts to run it on water.
and one that generates a random uuid means you lose the convenient semantics of a non-existant element being equivalent to the (trivially) invalid one.
1. Well, "generates a random uuid" is pretty much what I want from uuid. Therefore, I want this functionality in the most convenient form (apart from that long and boring talk about value- vs. pointer-semantics). 2. I do not see you losing anything as that non-existent is still available although in a more explicit form (which is good from the long-term maintenance point) -- uuid::null().
(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".
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. 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. Best, V.

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

What makes the Nil UUID not "valid"?
Scott, I suspect you are asking the questions you already know the answers to. Still I'll try answering them. Nil is invalid in the same way as NULL is invalid -- it does not deliver the main properties the class has been designed for. nil and NULL are specifically introduced to be different and distinguishable from normal "valid" objects. *That* makes them "invalid".
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.
Yes, I hope there will "be uuids::nil() to get an instance of it" and I'd like that to be the only get-nil mechanism. I thought the original discussion centered around the default constructor behavior and to me that behavior is very important what the default constructor does.
If it's "invalid", why is it still available?
It is available for the same reason NULL and -1 (for many C functions) (and many other objects designated as "invalid") are available and I really do not want to be elaborating obvoius things of that kind. Still, it does not make uuid::nill() or uuid a pointer.
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;
To me it is application-specific what any constructor does or does not, generation or no generation.
They all have T() == T();
So what? The fact that for some classes T() == T() is purely coincidental. Or is it in the Standard? I'll promptly agree/surrender if it is a Standard requirement.
And they all are rarely useful as-is.
An 0 integer and an empty string "are rarely useful as-is"? I do not understand what you mean.
I'd argue that pointers do have value semantics.
I am not sure what to say to that.
swap(int *, int *) can only swap the pointees, not the pointers themselves.
In fact, swap(int* p1, int* p2) swaps the *pointers* because after the call p1 will have the value of p2 and p2 will have the value of p1.
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).
The same concept is likely to have different meanings in different areas and on different absraction levels. My interpretation of a pointer is pretty much as Wikipedia desribes it: In computer science, a pointer is a programming language data type whose value refers directly to (or "points to") another value stored elsewhere... For me UUID does not fit *that* definition.
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.
I'd appreciate if you could clarify the following for me: Is value_type() == value_type() an assumpton you made, or is it an assumption you know the committee made or the committee stated somewhere that was an expectation or even a requirement for not following which would be a *breaking* change? Could you please indicate any documents or parts of the Standard (I could not find anything of that kind)? And what exactly "my-style" UUID is breaking? These are honest questions. We've been using "my-style" UUID quite extensively (over a year) in our project and would like to know what exactly we are breaking. Apologies that I snipped and left unanswered quite a bit of your replies. They seem to cover too many different areas/issues and I simply have no capacity to agrue of such a wide front. So, I am happy you have it your way. Best, V.

On Sat, Dec 20, 2008 at 21:22, Vladimir Batov <batov@people.net.au> wrote:
I'd argue that pointers do have value semantics.
I am not sure what to say to that.
swap(int *, int *) can only swap the pointees, not the pointers themselves.
In fact, swap(int* p1, int* p2) swaps the *pointers* because after the call p1 will have the value of p2 and p2 will have the value of p1.
I meant that as the declaration, not a call. A function with signature (int *, int *) -- the only way to pass a pointer to a function -- cannot swap the pointers, only the pointees. To swap the pointers themselves requires a function with signature (int *&, int*&), which is passing references-to-pointers, not pointers. So I consider pointers to have value semantics, and their pointees to have a kind of reference semantics.
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).
The same concept is likely to have different meanings in different areas and on different absraction levels. My interpretation of a pointer is pretty much as Wikipedia desribes it: In computer science, a pointer is a programming language data type whose value refers directly to (or "points to") another value stored elsewhere... For me UUID does not fit *that* definition.
Ok. If I change my argument to use "resource handle" (at a similar abstraction level to Iterator) instead of "pointer" (where a pointer is one kind of resource handle, as are socket handles, texture handles in OpenGL, and many other things) do you follow what I'm trying to say? This is the base of my thinking about UUIDs, so I agree, we're talking at cross purposes until I can find a way to communicate it well.
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.
I'd appreciate if you could clarify the following for me: Is value_type() == value_type() an assumption you made, or is it an assumption you know the committee made or the committee stated somewhere that was an expectation or even a requirement for not following which would be a *breaking* change? Could you please indicate any documents or parts of the Standard (I could not find anything of that kind)? And what exactly "my-style" UUID is breaking? These are honest questions. We've been using "my-style" UUID quite extensively (over a year) in our project and would like to know what exactly we are breaking.
I consider it an implied assumption by the committee, though it's not an explicit requirement of any sort. This is based on the following 4 statements, which I consider true: 1) The committee does not like to have the semantics of well-formed programs in one revision of the standard have different semantics in a later revision, and as such will only make such changes if they are unavoidable or, possibly, if they are determined to not affect any cases of consequence. 2) In C++03, resize(sz) on a vector is defined (through a default argument) to be equivalent to resize(sz, value_type()) [vector.capacity], so any added elements are copy-constructed from the single default-constructed instance, so they are all equivalent (by container element requirement on the copy constructor). 3) In n2798, the C++0x draft from the 2008-10 mailing (as well as in previous drafts n2723 and n2588), there are 1- and 2-parameter overloads for resize on a vector [vector.capacity], and resize(sz) is defined, in the case where sz > size(), to "[append] sz - size() default constructed elements to the sequence". 4) If two default-constructed instances are not equivalent, then the change to [vector.capacity] changes the semantics of a well-formed program. Since this change was made to the standard and it's a relatively minor one, my impression is that the committee believes that any value_type where default-constructed instances are not equivalent is not a case of any consequence. But there are, as you mention, basically no requirements placed on default constructors for container element types, so my conclusion hangs on the strictness of application of statement 1 by the library working group. ~ Scott

On Sat, Dec 20, 2008 at 22:31, Scott McMurray <me22.ca+boost@gmail.com> wrote:
1) The committee does not like to have the semantics of well-formed programs in one revision of the standard have different semantics in a later revision, and as such will only make such changes if they are unavoidable or, possibly, if they are determined to not affect any cases of consequence.
Sorry, I just realized I'm using the wrong term here. (Well-formed programs can invoke UB, and removing UB is obviously something the committee likes, where reasonable.) It should read, "The committee does not like for programs with well-defined semantics (i.e. those that are well-formed and invoke neither undefined, implementation-defined, nor unspecified behaviour) in one revision of the standard have different semantics in a later revision, and ..."

A function with signature (int *, int *) -- the only way to pass a pointer to a function -- cannot swap the pointers, only the pointees. To swap the pointers themselves requires a function with signature (int *&, int*&), which is passing references-to-pointers, not pointers.
So I consider pointers to have value semantics, and their pointees to have a kind of reference semantics.
Yes, it is understood that in that context (or on that level) pointers do have value semantics and they are copied as, say, integers, etc. etc. Although I think it was not exactly the context in which I was discussing value/pointer behavior differences. ...
I'd appreciate if you could clarify the following for me: Is value_type() == value_type() an assumption you made, ...
I consider it an implied assumption by the committee, though it's not an explicit requirement of any sort. This is based on the following 4 statements, which I consider true:
1) The committee does not like to have the semantics of well-formed programs in one revision of the standard have different semantics in a later revision, and as such will only make such changes if they are unavoidable or, possibly, if they are determined to not affect any cases of consequence.
2) In C++03, resize(sz) on a vector is defined (through a default argument) to be equivalent to resize(sz, value_type()) [vector.capacity], so any added elements are copy-constructed from the single default-constructed instance, so they are all equivalent (by container element requirement on the copy constructor).
3) In n2798, the C++0x draft from the 2008-10 mailing (as well as in previous drafts n2723 and n2588), there are 1- and 2-parameter overloads for resize on a vector [vector.capacity], and resize(sz) is defined, in the case where sz > size(), to "[append] sz - size() default constructed elements to the sequence".
4) If two default-constructed instances are not equivalent, then the change to [vector.capacity] changes the semantics of a well-formed program.
Since this change was made to the standard and it's a relatively minor one, my impression is that the committee believes that any value_type where default-constructed instances are not equivalent is not a case of any consequence.
But there are, as you mention, basically no requirements placed on default constructors for container element types, so my conclusion hangs on the strictness of application of statement 1 by the library working group.
Thank you for your thorough explanations. Much and truly appreciated. In fact, I feel much better as I was worrird that there was something lurking in our usage of UUID (with "my" default-constructor behavior). I really like your attidude towards the Standard but I hate to say that your interpretation of it left me unconvinced. I searched high and low and I was not able to find any hints about equivalency of the default-constructed objects (required, expected, assumed or otherwise). And I am still under impression that that requirement (or expectation) cannot be real as many classes are not even meant to be comparable. I do not think it makes them unusable with the std containers or breaking anything or second-class classes of no interest to the Standards committee. Best, V.

On Sun, Dec 21, 2008 at 00:38, Vladimir Batov <batov@people.net.au> wrote:
[...] And I am still under impression that that requirement (or expectation) cannot be real as many classes are not even meant to be comparable. I do not think it makes them unusable with the std containers or breaking anything or second-class classes of no interest to the Standards committee.
I was careful to say equivalent, not equal, since even when things don't have any kind of comparison operator, C++03 still has the requirement that a copy of an object (whether from copy-construction or assigned) must be equivalent to the original, so the concept is there. Anyways, did you have any comments on my view of UUIDs as resource handles?

I was careful to say equivalent, not equal, since even when things don't have any kind of comparison operator, C++03 still has the requirement that a copy of an object (whether from copy-construction or assigned) must be equivalent to the original, so the concept is there.
Yes, I understand that you've been talking about equivalency. My point was that I was under impression you were extending (unduly IMHO) the equivalency (a requirement for the copy contructor) onto comparability as you were stressing that for the following the first 10 entries will end up different from the last 10: v.resize(10, uuid()) v.resize(20, uuid()) The equivalency requirement is about copying those second parameters into 'v'. And we are safe there, aren't we? There is no mention of all 20 being comparably equal (and in general terms we cannot expect that as op==() is not a given). Yes, it indeed looks somewhat weird if we are to treat classes as they were integers. Still, that "weirdness" does not prevent uuid with "my" behavior to be a regular type as per Stepanov: "... equality is defined through a pair-wise equality of the corresponding parts. I call objects satisfying such laws regular."
Anyways, did you have any comments on my view of UUIDs as resource handles?
Well, I think you have a point about UUIDs as resource handles. To me that view is a *deployment* view (like a key in std::map can be interpreted as a "resource handler", "pointer" to the actual resource). On that somewhat architectural level the notion of a "pointer" (or "resource handler") is obviously very different from and much broader than "my" view -- the *behavioral* view of the class itself. What I'd like to do in the discussion is to somewhat step back and clarify my initial goal. I "jumped" in with a clear intention to look at the review from the average user point of view. That is the position many are overwhelmingly in (using myriads of libraries developed by others). Therefore, I intentionally wanted to limit myself to the interface and the interface through the user's (not the developer's) eyes. From that point of view I felt that it was important to stay focused on the interface so that is complete but minimal, consistent and intuitive, unambiguous, with the minimum of new vocabulary and minimum of coupling. That's why I was happy to see conversions to/from string branched off into an orthogonal functionality (lexical_cast). That's why I would not want to see generators convertible into or aware of uuids. That's why I'd like to see all constructors behaving consistently and by the book. That's why I'd like to see a *special* uuid::nil() created via different "special" and explicit means. That's why I'd like to see the most convenient deployment operator (the def. cnstr) behaving as-others and assigned to the most used deployment pattern (IMHO of course). Now if this primary goal contradicts some subtle behavior (which I am convinced of :-) ), let's work around that to accommodate *both* sides (in fact, I'd still favored the user side as the user will be ultimately deciding the success/failure of our effort). With regard to the default constructor it seems like disabling it would be the best compromise. I hope we won't be arguing that all classes must have the default constructor so that we could use operator[] on std::map<string, some-class>. Best, V.

on Sat Dec 20 2008, "Vladimir Batov" <batov-AT-people.net.au> wrote:
I searched high and low and I was not able to find any hints about equivalency of the default-constructed objects (required, expected, assumed or otherwise). And I am still under impression that that requirement (or expectation) cannot be real as many classes are not even meant to be comparable.
I believe the general consensus about that is changing, at least somewhat.
I do not think it makes them unusable with the std containers or breaking anything or second-class classes of no interest to the Standards committee.
You might Google for 'stepanov "regular types"' -- Dave Abrahams BoostPro Computing http://www.boostpro.com

I searched high and low and I was not able to find any hints about equivalency of the default-constructed objects (required, expected, assumed or otherwise). And I am still under impression that that requirement (or expectation) cannot be real as many classes are not even meant to be comparable.
I believe the general consensus about that is changing, at least somewhat.
Dave, would you please elaborate? In what "direction" that is changing? Are there plans to single the def. cnstr out and to impose some restrictions/requirements/assumptions onto it?
I do not think it makes them unusable with the std containers or breaking anything or second-class classes of no interest to the Standards committee.
You might Google for 'stepanov "regular types"'
Thank you for the pointers. I was able to download "Fundamentals of Generic Programming" and there they clearly consider the def. cnstr one of the fundamental operations for built-in types that they are extending on to "regular types". I have to say that my initial reaction is of a surprise. I would expect it the other way around -- I'd try making built-in types behave more like "normal" classes or if we cannot do that for backward compatibility reasons, then acknowledge the difference and get on with it. Dragging "normal" classes down and squezing them in to the the procrustean bed of the built-in types (dragged into the present through C compatibility) seems like a step down. I am not sure of the practical importance of that Stepanov's view as in many years of writing code I always followed the view of only introducing necessary constructors and I do not remember being hampered by the fact that those classes were not "regular types". What in your view is the importance of that "regular type"-conformance (or non-conformance)? Thanks, V.

Just to drop my 2 cents. I'd like to have the following use case be efficient. class foo { foo( const char* filename ) { std::ifstream file(filename); file >> m_uuid; } private: uuid m_uuid; }; This seems like a reasonable usage pattern to me and I would be very surprised if it was more expensive than reading into a char array. -- Michael Marcin

Just to drop my 2 cents.
I'd like to have the following use case be efficient.
class foo { foo( const char* filename ) { std::ifstream file(filename); file >> m_uuid; }
private: uuid m_uuid; };
This seems like a reasonable usage pattern to me and I would be very surprised if it was more expensive than reading into a char array.
Just for clarity I'll state the obvious -- in your example 'm_uuid' *is* initialized. Only built-in types allow declaration without definition (as a place holder). Classes do not allow that behavior. Classes always require and call constructors. Therefore, IMHO making them *look* like they behave similarly to built-ins sends/enforces the wrong message/impression. Therefore, I prefer the following which does exactly the same -- 'm_uuid' initialized invalid -- but explicit about what it actually does. class foo { foo( const char* filename ) : m_uuid(uuid::nil()) { std::ifstream file(filename); file >> m_uuid; } private: uuid m_uuid; }; As for efficiency I expect this variant to be at least as efficient (if not better) because uuid::nil() is likely to have and to return the same initialized-once instance, i.e. no byte initialization traversal as in uuid() /* throw() */ { for (data_type::iterator i=data_.begin(); i!=data_.end(); *i = 0, ++i); } Then, when a copy constructor is applied, it'll likely to copy the internal data array not per-byte but per-integer (4 bytes), i.e. 4 times quicker (not to mention the traversal overhead). Best, V.

Vladimir Batov wrote:
Just to drop my 2 cents.
I'd like to have the following use case be efficient.
class foo { foo( const char* filename ) { std::ifstream file(filename); file >> m_uuid; }
private: uuid m_uuid; };
This seems like a reasonable usage pattern to me and I would be very surprised if it was more expensive than reading into a char array.
Just for clarity I'll state the obvious -- in your example 'm_uuid' *is* initialized.
I never said otherwise.
Only built-in types allow declaration without definition (as a place holder). Classes do not allow that behavior. Classes always require and call constructors. Therefore, IMHO making them *look* like they behave similarly to built-ins sends/enforces the wrong message/impression. Therefore, I prefer the following which does exactly the same -- 'm_uuid' initialized invalid -- but explicit about what it actually does.
I realize invalid vs nil/null was discussed earlier but I still don't like using those terms interchangeably.
As for efficiency I expect this variant to be at least as efficient (if not better) because uuid::nil() is likely to have and to return the same initialized-once instance, i.e. no byte initialization traversal as in
uuid() /* throw() */ { for (data_type::iterator i=data_.begin(); i!=data_.end(); *i = 0, ++i); }
Then, when a copy constructor is applied, it'll likely to copy the internal data array not per-byte but per-integer (4 bytes), i.e. 4 times quicker (not to mention the traversal overhead).
Surely that should at the very least be a std::fill which gets optimized down to the a memset when possible (by VC9 at least). But is it more correct to do this initialization than applying data_type's default initialization? Just to be clear this does or doesn't seem like a reasonable use case to you? -- Michael Marcin

But is it more correct to do this initialization than applying data_type's default initialization?
I do not feel the def. cnstr is in order here. Because what we say in the code is -- we do not need an initialized instance (with the def. or any other cnstr) but a place-holder only that we'll initialize ourselves via op>>.
Just to be clear this does or doesn't seem like a reasonable use case to you?
IMHO it is certainly a reasonable use-case. That is, in fact, why I think it should be saying what it is doing. In general terms, for some other class a def. cnstr might be expensive or even unavailable. That should not be stopping us from applying the use-case. Just my view as always. Best, V.

the same -- 'm_uuid' initialized invalid -- but explicit about what it actually does.
I realize invalid vs nil/null was discussed earlier but I still don't like using those terms interchangeably.
I've been wondering where that "invalidity" property of nil/null comes from for me? For me it certainly started with K&R stating "The symbolic constant NULL is ... to indicate more clearly that this is a special value for a pointer." That is, it was not just another but special pointer to start with. Then, through many years, that "special" property has been firmly ingrained for me as "invalid" due to char* p1; char* p2 = NULL; char* p3 = "mama"; strlen(p1); // bombs strlen(p2); // bombs strlen(p3); // good For all practical purposes the above makes p1 and p2 quite naughty/bad/invalid compared to the well-behaving p3. Despite NULL's quite official status in the language(s), strlen() clearly "thinks" that NULL is anything but valid and bombs spectacularly. Then, I habitually extended that notion onto any foo::null() and ultimately to uuid::nil(). Anyone with more descriptive suggestions? Best, V.

On Tue, Dec 23, 2008 at 18:27, Vladimir Batov <batov@people.net.au> wrote:
For all practical purposes the above makes p1 and p2 quite naughty/bad/invalid compared to the well-behaving p3. Despite NULL's quite official status in the language(s), strlen() clearly "thinks" that NULL is anything but valid and bombs spectacularly. Then, I habitually extended that notion onto any foo::null() and ultimately to uuid::nil().
I don't really have any strong objections to your description insofar as it applies to char* in the context of "C Strings", but I completely disagree with extending it to uuid. find_by_uuid(uuids::nil()); and find_by_uuid(uuids::native_generator()()); will behave identically (assuming you don't have anything keyed by nil -- which you shouldn't -- and assuming you don't hit a collision -- which a time- and mac-address-based UUID effectively prevents). So how is nil any less valid? Your belief for char* comes from preconditions on common functions, so what functions (and thereby preconditions) are you using to apply the same reasoning to UUID?

Scott,
I don't really have any strong objections to your description insofar as it applies to char* in the context of "C Strings", but I completely disagree with extending it to uuid.
Yes, I think I've explained that my usage of it is mostly historical and habitual. Also, we've established that "invalid" is not the ideal description. Still, I cannot see anyone coming out with a better name to classify nil, null and the like. Until then, I guess, I'll probably keep using "invalid" (not in spite but merely as the closest *I* can come up with). Consider my usage of it in broader sense (as you did with pointers). By now we all understand in what context it is used and we can put this one to rest -- we still have plenty to debate about, right? Peace, V.

On Tue, Dec 23, 2008 at 20:10, Vladimir Batov <batov@people.net.au> wrote:
I don't really have any strong objections to your description insofar as it applies to char* in the context of "C Strings", but I completely disagree with extending it to uuid.
Yes, I think I've explained that my usage of it is mostly historical and habitual. Also, we've established that "invalid" is not the ideal description. Still, I cannot see anyone coming out with a better name to classify nil, null and the like. Until then, I guess, I'll probably keep using "invalid" (not in spite but merely as the closest *I* can come up with). Consider my usage of it in broader sense (as you did with pointers). By now we all understand in what context it is used and we can put this one to rest -- we still have plenty to debate about, right?
could be overloaded to call lexical_cast<uuid<aggregate> >, the fastest option. People could have map<string, uuid<nil_default> > if
Amusingly, "null" is listed as a synonym for "invalid" (in http://thesaurus.reference.com/browse/invalid ). "Specious" is close to working, and borrowing "singular" from iterators almost fits, but I can't come up with a great name either. And I think the problem here is that we really don't have that much to debate about (which isn't really a problem, I suppose). The only thing we disagree about is what the default constructor should do, which is why we started debating about whether the nil UUID was "invalid", since we all (as far as I can tell) agree that a default constructor shouldn't put something in an invalid state, just not what was "invalid". To rephrase the big problem I have with making the default constructor call a generator: to me, it feels like making shared_ptr<T>() do the same as shared_ptr<T>(new T()). Sure, you could do it. Sure, it saves people calling new themselves. Sure, you could then have shared_ptr<T>::null() for the null pointer. And I think it could even be done without breaking a fairly large fraction of programs. But nobody would *ever* advocate that. It'd just be a weird thing to do. But there's always the other way to resolve a disagreement over semantics: parametrization! What if there was an initialization policy? boost::uuid could be boost::uuids::uuid<no_default>, since I think there are a fairly large number of use cases that don't need one at all. lexical_cast<uuid<T> they want the "if (m[foo])" shortcut. And you could have uuid<native_generator>. The obvious question, then, is whether that's overkill :P ~ Scott

Amusingly, "null" is listed as a synonym for "invalid" (in http://thesaurus.reference.com/browse/invalid ).
Would you look at that! :-) LOL
"Specious" is close to working, and borrowing "singular" from iterators almost fits, but I can't come up with a great name either.
Well, I've never even heard of "specious" and "singular" does not seem to reflect nil's main property -- valuelessness (oh, this one is not probably good either).
And I think the problem here is that we really don't have that much to debate about (which isn't really a problem, I suppose). The only thing we disagree about is what the default constructor should do, which is why we started debating about whether the nil UUID was "invalid", since we all (as far as I can tell) agree that a default constructor shouldn't put something in an invalid state, just not what was "invalid".
You presented something like uuid::uuid(uuid::nil) which I do not object to -- my objections are to the ambiguity of the default constructor creating a special uuid::nil. Although come to think of it I think I probably still like the static uuid::nil() better as it returns ready to go uuid that I can return as in uuid do_something() { ... return uuid::nil(); } when in your variant it'll be like uuid do_something() { ... return uuid(uuid::nil); } unless uuid(uuid::nil) is not explicit. Then, uuid do_something() { ... return uuid::nil; // Implicit construction. }
To rephrase the big problem I have with making the default constructor call a generator: to me, it feels like making shared_ptr<T>() do the same as shared_ptr<T>(new T()). Sure, you could do it.
Well, I would not do that for shared_ptr as it explicitly has been designed to behave as a pointer. But I do do that for objects that are not pointers but happen to have pointer-semantics (namely my pimpl proposal in the vault).
Sure, it saves people calling new themselves. Sure, you could then have shared_ptr<T>::null() for the null pointer.
Yes, for pimpls I do exactly that. Again, I would not do that for shared_pre as it was designed to be a smart pointer with close to raw pointer semantics.
And I think it could even be done without breaking a fairly large fraction of programs. But nobody would *ever* advocate that. It'd just be a weird thing to do.
Well, I did and proposed exactly that :-), isn't that funny? Not for shared_ptr but for pimpl-based classes (http://www.ddj.com/cpp/205918714).
But there's always the other way to resolve a disagreement over semantics: parametrization!
could be overloaded to call lexical_cast<uuid<aggregate> >, the fastest option. People could have map<string, uuid<nil_default> > if
What if there was an initialization policy? boost::uuid could be boost::uuids::uuid<no_default>, since I think there are a fairly large number of use cases that don't need one at all. lexical_cast<uuid<T> they want the "if (m[foo])" shortcut. And you could have uuid<native_generator>.
The obvious question, then, is whether that's overkill :P
The main criterion for me is if the interface takes the guess-work out of equation for the user. If every constructor clearly states what it does without ambiguity, me happy. So, with things as you spelled out above, can we see how your proposed interface looks? Best, V.

But there's always the other way to resolve a disagreement over semantics: parametrization!
could be overloaded to call lexical_cast<uuid<aggregate> >, the fastest option. People could have map<string, uuid<nil_default> > if
What if there was an initialization policy? boost::uuid could be boost::uuids::uuid<no_default>, since I think there are a fairly large number of use cases that don't need one at all. lexical_cast<uuid<T> they want the "if (m[foo])" shortcut. And you could have uuid<native_generator>.
It's got me thinking about uuid parametrization and I think there is part of it that I am warming up to and the other part that I am unsure of. What I think we indeed could parameterize on might be the generator, i.e. template<class Generator> uuid { ... }; Reasoning behind it is that it is probably most likely that the developer once chosen a particular generator will stick with it throughout its application rather than juggling different generators at run-time. Therefore, parameterizing of the generator seems to make the code more solid and easier to maintain as the variability (and the room for an error -- calling the wrong generator) out of the equation. The "bad" thing is that this seem to better fit the def. cnstr (with generating behavior): template<class Generator> uuid { typedef ... nil_type; static nil_type nil; uuid() { typename Generator::range range = Generator::generate(); this->initialize(range.begin(), range.end()); } uuid(nil_type) { for (boost::array<char, 16>::iterator i = data.begin(), end = data.end(); i != data.end(); *i = 0, ++i); } }; As for parameterization of policy, then I am honestly not sure. As a proof of the concept it indeed sounds good. My fear though is that for maintainability/readability purposes that might be a nightmare. Having said that, that my view about hte def. cnstr and its behavior is just my view. If there is a good number of people thinking that the def. cnstr wih the nil behavior might be better/more practical, we should adapt the view, clearly document that and move forward. The certainly good part of it it'll make a lot of things easier to implement. Best, V.

Vladimir Batov wrote:
the same -- 'm_uuid' initialized invalid -- but explicit about what it actually does.
I realize invalid vs nil/null was discussed earlier but I still don't like using those terms interchangeably.
I've been wondering where that "invalidity" property of nil/null comes from for me? For me it certainly started with K&R stating "The symbolic constant NULL is ... to indicate more clearly that this is a special value for a pointer." That is, it was not just another but special pointer to start with. Then, through many years, that "special" property has been firmly ingrained for me as "invalid" due to
char* p1; char* p2 = NULL; char* p3 = "mama";
strlen(p1); // bombs strlen(p2); // bombs strlen(p3); // good
For all practical purposes the above makes p1 and p2 quite naughty/bad/invalid compared to the well-behaving p3. Despite NULL's quite official status in the language(s), strlen() clearly "thinks" that NULL is anything but valid and bombs spectacularly. Then, I habitually extended that notion onto any foo::null() and ultimately to uuid::nil().
Sorry for the late response. I disagree that NULL is invalid. Reading or comparing against the value of a NULL pointer is valid but not so against an uninitialized pointer. There are functions other than strlen where NULL does not bomb but has a well defined meaning and purpose, like strtok. FWIW uninitialized seems to be considered singular by the standard 24.1/5 [Example: After the declaration of an uninitialized pointer x (as with int* x;), x must always be assumed to have a singular value of a pointer. ] -- Michael Marcin

on Mon Dec 22 2008, "Vladimir Batov" <batov-AT-people.net.au> wrote:
Only built-in types allow declaration without definition (as a place holder). Classes do not allow that behavior. Classes always require and call constructors.
I don't think that's true of POD classes. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On Tue, Dec 23, 2008 at 01:24, Vladimir Batov <batov@people.net.au> wrote:
Just for clarity I'll state the obvious -- in your example 'm_uuid' *is* initialized. Only built-in types allow declaration without definition (as a place holder). Classes do not allow that behavior. Classes always require and call constructors.
"Declaration without definition" is extern. I think you mean uninitialized, which *is* allowed in classes, if they're aggregates. I've already uploaded a version that does exactly that.
Therefore, IMHO making them *look* like they behave similarly to built-ins sends/enforces the wrong message/impression.
So why not make them behave like them? I've always heard "do as the ints do" as the guideline for value types.
Therefore, I prefer the following which does exactly the same -- 'm_uuid' initialized invalid -- but explicit about what it actually does.
[code snip]
As for efficiency I expect this variant to be at least as efficient (if not better) because uuid::nil() is likely to have and to return the same initialized-once instance
I'd be very suspicious of that claim. The cache miss for going to get that instance could well be worse.
i.e. no byte initialization traversal
If you allow a constructor for making the nil value (obviously I would like the default constructor, but a function pointer so you could do uuid(uuids::nil) would be fine in your way), then there's no need for uuid::nil() to have a loop, since it can value-initialize the member array (which is an aggregate) letting the compiler use the most efficient zeroing method it has. Also, in a situation where the constructor logic is unneeded, it's possible to completely remove one that simply value-initializes. The compiler can never remove calls to a global static PRNG (which has cache and threading consequences, too).

AMDG Scott McMurray wrote:
As for efficiency I expect this variant to be at least as efficient (if not better) because uuid::nil() is likely to have and to return the same initialized-once instance
I'd be very suspicious of that claim. The cache miss for going to get that instance could well be worse.
msvc 9.0 compiles boost::array<char, 16> data; for(boost::array<char, 16>::iterator i = data.begin(), end = data.end(); i != data.end(); ++i) { *i = '\0'; } into xor eax,eax mov dword ptr [esp],eax mov dword ptr [esp+4],eax mov dword ptr [esp+8],eax mov dword ptr [esp+0Ch],eax In Christ, Steven Watanabe

"Declaration without definition" is extern.
Re-read Stroustrup's 4.9 and indeed I mis-interpreted "Any declaration that specifies a value is a definition". After close reading I see that char ch; int i; which I referred to as "declarations without definition" are indeed definitions. Should have been using something like "definition without value/initialization".
I think you mean uninitialized, which *is* allowed in classes, if they're aggregates. I've already uploaded a version that does exactly that.
Yes, I meant uninitialized. And I indeed remember that "classes, if they're aggregates" are allowed uninitialized. I am not a lawer though and I was not writing a specification. So, I was hoping the context was understood -- I was talking about mainstream "normal" classes and not those clearly dragged in due to backward compatibility with C. Consequently, I omitted that fringe (in my view and usage pattern) case.
Therefore, IMHO making them *look* like they behave similarly to built-ins sends/enforces the wrong message/impression.
So why not make them behave like them? I've always heard "do as the ints do" as the guideline for value types.
Because I do not believe we can. The distinctive difference between built-ins and classes (well, classes which are not aggregates. Pls let me skip mentioning that every time I say "class") is that built-ins (and class aggregates) allow "definition without initialization" and classes do not (well, classes which are not aggregates. Pls let me not mention that every time I say "class"). I.e. the two below do different things and carry different overhead: int i; // build-in uninitialized. Kind of a place-holder Foo foo; // class. default initialized stream >> i; stream >> foo; As for "why not make them behave like them", I had that crazy idea too dreaming why could not C++ introduce something like 'noninvariant' -- "defined uninitialized" for classes. Then, int i; // non-initialized place-holder Foo::noninvariant foo; // non-initialized place-holder stream >> i; stream >> foo; Then, I'd think we'd reconcile built-ins and classes once and for all. That functionality is needed as we've been using some sort of it with placement new(). However, that might be too far-fetched (and maybe troublesome) a dream. In the end we seem to get-by as it is. We call Foo foo; stream >> foo; if Foo::Foo() is acceptable or, say, Foo foo = Foo::null(); stream >> foo; if for whatever reason Foo::Foo() is not good/available. In uuid context it might be boost::uuid uuid(uuid::nil); stream >> uuid; That seems as close as we can get to mimic built-ins behavior.
If you allow a constructor for making the nil value (obviously I would like the default constructor, but a function pointer so you could do uuid(uuids::nil) would be fine in your way), then there's no need for uuid::nil() to have a loop, since it can value-initialize the member array (which is an aggregate) letting the compiler use the most efficient zeroing method it has.
I do not insist on m_uuid(uuid::nil()) initialization. I insist (well, if I can insist on anything) on making a uuid::nil explicit. Your suggestion looks good to me. Best, V.

on Sun Dec 21 2008, "Vladimir Batov" <batov-AT-people.net.au> wrote:
I searched high and low and I was not able to find any hints about equivalency of the default-constructed objects (required, expected, assumed or otherwise). And I am still under impression that that requirement (or expectation) cannot be real as many classes are not even meant to be comparable.
I believe the general consensus about that is changing, at least somewhat.
Dave, would you please elaborate? In what "direction" that is changing? Are there plans to single the def. cnstr out and to impose some restrictions/requirements/assumptions onto it?
Not exactly, but concept Regular is going to be an important one in C++0x.
I do not think it makes them unusable with the std containers or breaking anything or second-class classes of no interest to the Standards committee.
You might Google for 'stepanov "regular types"'
Thank you for the pointers. I was able to download "Fundamentals of Generic Programming" and there they clearly consider the def. cnstr one of the fundamental operations for built-in types that they are extending on to "regular types". I have to say that my initial reaction is of a surprise. I would expect it the other way around -- I'd try making built-in types behave more like "normal" classes
? You don't get to change the way built-ins work.
or if we cannot do that for backward compatibility reasons, then acknowledge the difference and get on with it. Dragging "normal" classes down and squezing them in to the the procrustean bed of the built-in types (dragged into the present through C compatibility) seems like a step down.
I admit I'm ambivalent about that too. The ability to cheaply default-construct an object is so seldom important, and strong invariants are so valuable, that I am reluctant to adopt the default-constructability rule. However, for move semantics (and what could be more suited to that than a UUID?) the invariant often needs to include a "empty" moved-from state anyway, and you could argue that one might as well implement a cheap, non-throwing default ctor that produces that state. The argument I sometimes use against it is that even though the empty state technically becomes part of the class invariant, limiting that state to moved-from objects still offers some improved ability to reason about code... but that's a weaker argument than I'd like.
I am not sure of the practical importance of that Stepanov's view
Personally, I'm wary of underestimating the importance of Stepanov's views.
as in many years of writing code I always followed the view of only introducing necessary constructors and I do not remember being hampered by the fact that those classes were not "regular types". What in your view is the importance of t hat "regular type"-conformance (or non-conformance)?
I'm still trying to form an opinion on it. Certainly the copy-constructability, assignability, and equality comparison requirements are required for equational reasoning. Default constructability doesn't rise to the same level of importance for me, but I'm still unsure. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Thank you for the pointers. I was able to download "Fundamentals of Generic Programming" and there they clearly consider the def. cnstr one of the fundamental operations for built-in types that they are extending on to "regular types". I have to say that my initial reaction is of a surprise. I would expect it the other way around -- I'd try making built-in types behave more like "normal" classes
? You don't get to change the way built-ins work.
Yes, I know. It was a typical statement when one gives you 2 choices when one is clearly out of question, i.e. you are in fact presented with no choice at all.
or if we cannot do that for backward compatibility reasons, then acknowledge the difference and get on with it. Dragging "normal" classes down and squezing them in to the the procrustean bed of the built-in types (dragged into the present through C compatibility) seems like a step down.
I admit I'm ambivalent about that too. The ability to cheaply default-construct an object is so seldom important, and strong invariants are so valuable, that I am reluctant to adopt the default-constructability rule.
I suspect it is worse. If a class cannot create a valid instance with a def. cnstr, then such an instance is essentially invalid, i.e. we are getting closer to the condemned "declaration without definition" behavior of the built-ins. More still, given we've open up a possibilty of incorrectly initialized object, we'll have to have additional checks throughout the code if the instance (or parts of it) is indeed valid.
However, for move semantics (and what could be more suited to that than a UUID?)
Well, actually I am not sure UUID is a really good candidate for move semantics -- it's just 16 bytes, self-contained, really cheap to copy. Move semantics makes sense for really big and expensive-to-copy objects. Still, I've been getting-by by passing by-reference or enveloped in auto_ptr or shared_ptr.
the invariant often needs to include a "empty" moved-from state anyway, and you could argue that one might as well implement a cheap, non-throwing default ctor that produces that state.
Well, from my mole-hill the medicine seems worse than the problem. If we truly need such a "special" state, I'd expect we say so explicitly and have something like foo::bad, foo::invalid, foo::uninitialized in general terms rather than hijack the def. cnstr. That way, we'd indeed mimic the "declaration without definition" built-in behavior without disrupting the status-quo. That uninitialized/invalid state could be used for the move semantics.
I am not sure of the practical importance of that Stepanov's view
Personally, I'm wary of underestimating the importance of Stepanov's views.
I did not mean to question the importance of Stepanov's view. :-) I meant to ask how big an impact that view might have for developers-practitioners. Best, V.

Dave,
However, for move semantics (and what could be more suited to that than a UUID?) the invariant often needs to include a "empty" moved-from state anyway, and you could argue that one might as well implement a cheap, non-throwing default ctor that produces that state. The argument I sometimes use against it is that even though the empty state technically becomes part of the class invariant, limiting that state to moved-from objects still offers some improved ability to reason about code... but that's a weaker argument than I'd like.
I just read "n2027: A Brief Introduction to Rvalue References" (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html) and there the move is done manually: // move semantics clone_ptr(clone_ptr&& p) : ptr(p.ptr) {p.ptr = 0;} That is, we manually take a resource (ptr) from the source (p), assign the resource to the target (this) and re-assign the target resource (p.ptr = 0). Therefore, I am glad to see that there seems *no* requirement to provide some special "empty" state and hijack the default constructor for the purpose -- it's up to the implementer to decide which state satisfy the need. Taking the discussed uuid as an example that might look like the following: uuid(uuid&& p) : data_(p.data_) { p = uuid::nil(); } or uuid(uuid&& p) { *this = uuid::nil(); std::swap(*this, p); } So, we seem to be safe and sound with the uuid default constructor (or lack of it) unaffected and irrelevant. Does it make sense? Best, V.
participants (5)
-
David Abrahams
-
Michael Marcin
-
Scott McMurray
-
Steven Watanabe
-
Vladimir Batov