
The example below shows a technique for perfect forwarding that seems to work with GCC 3.3.1. rvalues are always moved automatically, and lvalues are copied. Except for one rather important case, it works with Comeau (errors only in strict mode). The important case that causes the error clearly violates 12.2/1 of the standard, but see below. VC++ and CodeWarrior have different kinds of problems with it from Comeau/strict. I am hoping that they are wrong, but I am not enough of a core language jock to make a determination on which of these compilers is correct (if any). 12.2/1 says that the act of binding an rvalue to a (const) reference creates a temporary (which may be elided). The creation of the temporary requires the existence of a copy ctor with a const& argument. As far as I know, that rule serves no useful purpose, but I may have missed something. *If* my example actually works other than this case, I'd like to try to get the rule lifted, and soon! This is by no means the cleanest route to move semantics, but it's probably the smallest "language extension" we'd ever need to get it, and might even be possible before C++0x Enjoy, Dave -- Dave Abrahams Boost Consulting www.boost-consulting.com

On Fri, Feb 06, 2004 at 07:00:49PM -0500, David Abrahams wrote:
I had to Google for "perfect forwarding" to discover http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1385.htm http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1377.htm (which I'd read before but forgotten about). It is unclear to me if/how your code solves the "perfect forwarding" problem. Instead it appears to show a way to make a particular type "movable". Have I misunderstood? (How) does your generalize to a way to write forwarding functions? It seems you have a clever way to effectively "detect rvalue-ness"; the copy-constructor and the "pseudo-copy-constructor" (the thing with the enable_if) only swallow lvalues, whereas rvalues find a more attractive construction path via "ref". Is that a correct summary?
I am not sure what portion of the rule you don't like. Is it the fact that they point to a particular copy constructor (the const& one), rather than just any available/visible/something copy constructor? In test10, what do you 'want' to happen here: sink2( <a temporary object, that is, an rvalue> ); ? Overall it looks clever, but I don't yet understand exactly what it solves or the details of the 12.2/1 thing. -- -Brian McNamara (lorgon@cc.gatech.edu)

--On Saturday, February 07, 2004 12:59 AM -0500 Brian McNamara <lorgon@cc.gatech.edu> wrote:
[snip] Some additional background is available here: <http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1385.htm> - Mat

Brian McNamara <lorgon@cc.gatech.edu> writes:
I think so.
No, it's the fact that any additional temporary at all (and thus, a copy ctor) is needed.
In test10, what do you 'want' to happen here: sink2( <a temporary object, that is, an rvalue> );
It should bind a const reference to the temporary without complaining that the temporary can't be copied. -- Dave Abrahams Boost Consulting www.boost-consulting.com

On Sun, Feb 08, 2004 at 11:39:51AM -0500, David Abrahams wrote:
I see. What about const X& x = source(); // 12.2/5 ? Or something. I am trying to find the 'hard' cases where the compiler might not be able to easily make the temporary have the right 'lifetime', and thus require an extra copy. My guess is that the only reason that rule exists is that it somehow makes compiler writers' lives easier, so my hunch is you'll need to talk to (argue with :) ) them. -- -Brian McNamara (lorgon@cc.gatech.edu)

Brian McNamara <lorgon@cc.gatech.edu> writes:
That's exactly the case I'm talking about.
I don't see any problem here. There's no problem getting the rvalue out of source(), or sink(source()) would fail. Furthermore, no compiler I've ever seen will actually make the allowed temporary in that case. -- Dave Abrahams Boost Consulting www.boost-consulting.com

On Sun, Feb 08, 2004 at 03:07:05PM -0500, David Abrahams wrote:
I think we're on the same page, but to clarify--this case const X& x = source(); // 12.2/5 seems harder than this one sink(source()); to me, since in the former, the "temporary" object must have a lifetime that outlasts the full expression that created it.
compiler I've ever seen will actually make the allowed temporary in that case.
But if that's true, then I agree with you that I don't see "the point of the rule", so go lobby to change it and be the hero of many. :) -- -Brian McNamara (lorgon@cc.gatech.edu)

David Abrahams <dave@boost-consulting.com> writes:
Brian McNamara <lorgon@cc.gatech.edu> writes:
[snip]
FWIW, I think at least one version of the IBM Visual Age compiler disallows bindings to a noncopyable rvalue, possibly because it actually wants to create a temporary. All compilers *should* issue a diagnostic anyway, whether they actually need the temporary or not. See e.g. "Problem with struct wrapstrstream": http://lists.boost.org/MailArchives/boost/msg36290.php BTW, has anyone else noticed that all of Google's links to the boost mail archive are wrong at the moment? It looks like the messages have all been renumbered at some stage, invalidating all previous URLs (e.g. Google thinks the above message is at http://lists.boost.org/MailArchives/boost/msg09814.php). I got the up-to-date link from AltaVista. -- Raoul Gough. export LESS='-X'

Raoul Gough <RaoulGough@yahoo.co.uk> writes:
They were wrong a long time ago, then we embarked on an extensive project to fix them, so they're broken again ;-). But that's temporary, since google is "self-fixing". The real test is whether links in other boost messages work. That was our goal, and I think Jeff Squyres and Larry Meehan (the OSL sysadmins) actually achieved it. -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams <dave@boost-consulting.com> writes:
All the "next in thread" type links I've seen have been internally consistent. Unfortunately, this renumbering almost certainly means that external references to the old URL for a given message (e.g. on another mailing list or newsgroup) will be wrong. From what I've seen of Google's old links, you usually get a different message, not the one you expected and not a HTTP 404 for instance. It might have been (or still be) a good idea to invalidate completely the old style URLS to avoid confusion. Better still would have been to support both the old and new numberings in parallel, since you can't know what references exist for the old URLs. BTW, AltaVista's bot seems to revisit its indexed sites much more frequently than the GoogleBot does, which would explain why it's more up to date. -- Raoul Gough. export LESS='-X'

David Abrahams <dave@boost-consulting.com> writes:
How about struct X{}; struct Y { std::string s; X x; std::vector<int> v; }; Y ySource(); const X& x=ySource().x; This strikes me as more likely to cause a copy than a straight-forward binding from an X temporary. Anthony -- Anthony Williams Senior Software Engineer, Beran Instruments Ltd. Remove NOSPAM when replying, for timely response.

Anthony Williams wrote:
I tried your suggestion on several compliers. EDG and BCC didn't copy. VC copied using the copy ctor. GCC3.2 copied *without* using the copy ctor! <code> extern "C" int printf(const char*, ...); struct X { X() {} X(X const&) { printf("X(X const&)\n"); } }; struct Y { X arr[1000]; X x; ~Y() { printf("~Y(): %p\n", &x); } }; int main() { const X& x = Y().x; printf("X: %p\n", &x); return 0; } #if 0 VC: X(X const&) ~Y(): 0012FF74 X: 0012FB88 GCC: ~Y(): 0x22fec0 X: 0x22fec8 Comeau: X: 0012FE7C ~Y(): 0012FE7C BCC: X: 0012FF84 ~Y(): 0012FF84 #endif

Anthony Williams <anthony.williamsNOSPAM@anthonyw.cjb.net> writes:
I think the actual problem is one of either interpretation of 8.5.3/5 or overspecification in that very paragraph. If, instead of saying, "A temporary of type ``cv1 T2'' [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary" It said: "A temporary of type ``cv1 T2'' [sic] is constructed using direct initialization from the rvalue object. The reference is bound to the temporary or to a sub-object within the temporary" we'd be home free. -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
I'm not sure. When source and destinations un-cv types are the same or base/derived direct initialization and copy initialization are the same (per 8.5/14/4/2) and this fact was (viciously) exploited by the old auto_ptr. The only different I can think of is in the esoteric case in which the copying constructor is explicit. Rani

"Rani Sharoni" <rani_sharoni@hotmail.com> writes:
Something's missing from that sentence to make it comprehensible. It has the same grammatical structure as "When my hand is blue or my hair is green and I was a fool", which is not a complete sentence. -- Dave Abrahams Boost Consulting www.boost-consulting.com

"Rani Sharoni" <rani_sharoni@hotmail.com> writes:
I'm trying to say that I don't understand what you're saying in that "sentence", and I'm trying to explain why I don't understand it. The "sentence" is grammatically incomplete. I'd like you to explain what you're trying to say. -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
For some reason I thought that what I wrote is so trivial that even grammar errors will not hide my intentions. It seems that Christoph Ludwig understand my intentions. Direct initialization is the *same* as copy initialization when constructing object of the same type using non-explicit (i.e. converting) constructor. struct A { A(int); }; A a1(A(10)); // #1 A a2 = A(20); // #2 same as #1 Both initializations are the same per 8.5/14/4/2: <Q> If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). </Q> IMO your suggestion is fully compliant and EDG has bug that confuses everyone. I don't have anything else to add to this discussion. Thanks, Rani No alarms and not surprises silence.

"Rani Sharoni" <rani_sharoni@hotmail.com> writes:
I tend to be overly literal minded, and the missing comma after "derived" threw me off.
So you're basically saying that in my suggested text: "A temporary of type ``cv1 T2'' [sic] is constructed using direct initialization from the rvalue object. The reference is bound to the temporary or to a sub-object within the temporary" the mention of direct initialization is pointless and could be written : "A temporary of type ``cv1 T2'' [sic] is initialized from the rvalue object. The reference is bound to the temporary or to a sub-object within the temporary" ?? I think the text has to account for the possibility that the constructor used might be explicit, doesn't it?
Sorry to draw it out, but I don't understand when you say "suggestion" whether you're referring to my suggested text change or the movable class idiom I posted.
Wow, parse error again. Radiohead? -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
This is true but explicit copy (or copying) constructor is quite useless (unless someone will find a way to abuse it for good purpose). Raoul Gough and I have tried to relax this surprising reference binding limitation (i.e. requires copying constroctor) for the favor of wider range of cases (e.g. private copy constructor). The suggestion basically says that *direct reference binding* is enough for most cases of class rvalue bindings. http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/cwg_active.html#391
Maybe some more.
The later. For the moveable class Idiom you don't need to revise the standard text since Steve Adamczyk already did that ~7 years ago when he removed the copy constructor requirement. You only encountered a bug in EDG which is also inconsistent: struct A { // private: template<typename T> A(T const& r) { int const* p = &r; // error if T != int -> error if T == A p = p; } private: A(A&); }; int f(A const&); // template constructor is // enough for the "illusive coping" int x = f(A(10)); // compiled fine using EDG and GCC Making the template *converting* constructor private will trigger access error for both EDG and GCC although they don't actually call it. VC rejected this code and has open bug for this issue. Again DR #391: <Q> J. Stephen Adamczyk replied that the reason for changing "a copy constructor" to "a constructor" was to allow for member template *converting* constructors.</Q> In your moveable implementation the converting constructor (i.e. X(ref)) is as good as template converting constructor from overloading pov which makes your code *fully compliant* within the current standard text. Cheers!
No alarms and not surprises silence.
Wow, parse error again. Radiohead?
Right, I'm a loyal fan. Regards, Rani

"Rani Sharoni" <rani_sharoni@hotmail.com> writes:
Maybe it's useless, but it's detectable.
I wish you could get EDG, Metrowerks, and Microsoft (and others, I'm sure, including Peter Dimov) to agree with you. There appears to be an emerging consensus among implementors that a public non-const copy ctor is needed.
That in itself
Whoa. Is that different from MoJo? And how can I understand what you're saying well enough to convince implementors that I'm right? -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
And how can I understand what you're saying well enough to convince implementors that I'm right?
I think that the first thing to do is to ask implementors why they require the accessible copy constructor for f( X(1) ), while at the same time the seemingly equivalent f( XC(X(1)) ) (as per the infamous second bullet) compiles fine.

"Peter Dimov" <pdimov@mmltd.net> writes:
That argument won't fly with Microsoft, since in strict mode they reject f( XC(X(1)) ). And shouldn't they, after all? The problem is that operator ref() is non-const, so you can't apply the loophole again to copy XC(X(1)) [which ability is required per 8.5.3/5]. I don't find the failure of f(XC(X(1)) compelling, though, because IIUC the final XC conversion you've spelled out there really represents the last "copy" in the sequence of copies allowed to the compiler. Still I now have to wonder why this test passes VC7.1 in strict mode: X const& r = XC(); Since it is also binding a const reference to a const rvalue (which can't be copied using a copy ctor), while test 11 does not: sink2(csource()); -- Dave Abrahams Boost Consulting www.boost-consulting.com

On Tue, Feb 10, 2004 at 11:54:52AM -0500, David Abrahams wrote:
If you parse above sentence as When source and destinations un-cv types are the same or [are] base/derived [, then] direct initialization and copy initialization are the same (per 8.5/14/4/2) and this fact was (viciously) exploited by the old auto_ptr. then it seems perfectly comprehensible to me. I thought both ellipses were correct English, but I will leave this assessment to the native speakers. Christoph -- http://www.informatik.tu-darmstadt.de/TI/Mitarbeiter/cludwig.html LiDIA: http://www.informatik.tu-darmstadt.de/TI/LiDIA/Welcome.html

Christoph Ludwig <cludwig@cdc.informatik.tu-darmstadt.de> writes:
If you already know what the writer's intention was, maybe it's easy to insert those conjunctions. It's not clear to me that they're intended insertions. I read "base/derived direct initialization" as a single noun phrase beginning with an adjective.
I thought both ellipses
?? IIUC, this is an ellipsis: ... and I don't see any above. Maybe ellipsis really means "omission"...
were correct English
...but if so, omissions are correct only to the extent to which it doesn't harm comprehensibility.
, but I will leave this assessment to the native speakers.
-- Dave Abrahams Boost Consulting www.boost-consulting.com

On Wed, Feb 11, 2004 at 08:32:08AM -0500, David Abrahams wrote:
[...]
That's one possible meaning of "ellipsis".
and I don't see any above. Maybe ellipsis really means "omission"...
Well, back from my English lessons in school I remembered "ellipsis" as a technical term from linguistics. And the online Merriam-Webster (which I actually consulted before posting my last mail) seems to agree: It lists the omission of words from a sentence under point (1a): http://www.m-w.com/cgi-bin/dictionary?book=Dictionary&va=ellipsis If you suggest that "omission" would have been less likely to cause misunderstandings then I won't argue. But please, let's stop here. I had no intentions of starting an OT thread about the meaning of words. I only pointed out how to parse Rani's sentence in order to allow your technical discussion to go on. Christoph -- http://www.informatik.tu-darmstadt.de/TI/Mitarbeiter/cludwig.html LiDIA: http://www.informatik.tu-darmstadt.de/TI/LiDIA/Welcome.html

David Abrahams wrote:
Like Brian I don't understand how this solves perfect forwarding.
Isn't this the same rule that crippled Andrei's Mojo as well? The technique is basically the same, except he had something like: X(X&) // copy X(constant_ref<X>) // copy X(rvalue_ref<X>) // move IIRC this also worked on everything, except when binding rvalues to const&. -- Daniel Wallin

Daniel Wallin <dalwan01@student.umu.se> writes:
Sure, but the problem with Andrei's thing, IIRC, was that it was intrusive on *users* of X. If you wanted to accept an X and be able to move from it, you had to have an overload for rvalue_ref<X>. In this case, the class provides moving from rvalues all by itself. -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
In quite sure this isn't right. AFAICT the only thing that differs in the two solutions is how you discriminate const lvalues from rvalues. Andrei did it with two auto_ptr_ref style conversion operators, you do it with a templated constructor. With Mojo you would do something like: struct X : moveable<X> { X(X&); X(constant_ref<X>); X(rvalue_ref<X>); // move constructor }; And it would always move from rvalues. The intrusive part was that to solve the whole "binding rvalues to const&" problem you needed to return some special wrapper type: moved<X> f(); -- Daniel Wallin

David Abrahams wrote:
Maybe I misunderstood what you have done, but to me this was the most important part of your code! No strange changes for the user like returning special values from functions (which is BTW breaking RVO/NRVO and which is what I never liked about Mojo...) and no need for special function signatures as in Mojo, where you had to declare function in a certain unnatural style. IIRC, something like: void f( X& ); void f( mojo::temporary< X >& ); void f( mojo::constant< X >& ); This is now solved as your version is a true optimization, an implementation detail of the class with no need for the user to care about it. Regards, Daniel -- Daniel Frey aixigo AG - financial solutions & technology Schloß-Rahe-Straße 15, 52072 Aachen, Germany fon: +49 (0)241 936737-42, fax: +49 (0)241 936737-99 eMail: daniel.frey@aixigo.de, web: http://www.aixigo.de

Daniel Frey wrote:
I believe this is still needed, for the same reason as in Mojo.
You only need to do that if you want an rvalue reference as a parameter. Normally you would just take the parameter by value and trust the move constructor. The same thing needs to be done with Dave's version and also with some future addition of rvalue-references to C++. void f(X); // can move automatically with move constructor // in both Andrei's and Dave's code void f(mojo::temporary<X> x); // rvalue ref in Andrei's code void f(X::ref x); // rvalue ref in Dave's code -- Daniel Wallin

Daniel Wallin <dalwan01@student.umu.se> writes:
Not exactly. void f(X const&) Currently accepts rvalues of ordinary copyable classes, and that wouldn't change if the rvalue reference (&&) notation were introduced. The problem is that in order to use my technique (or Andrei's), movable classes can't be "ordinary copyable" classes; they must have a copy ctor taking a non-const rhs. At least with a language extension, an "ordinary copyable" class can also be movable. One thing I think we overlooked in the move proposal, though, is the fact that "only movable and not copyable" class rvalues *still* can't legally be passed where a const reference is expected, unless the rule in question is lifted. That seems like a problem to me. -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
Right. FWIW, I would love to see this rule go away.
That is true if the class would declare regular move constructors with rvalue references. But doesn't the trick used to implement move_ptr apply with the new rvalue references as well? (moving with const& constructor) http://tinyurl.com/3f6sw X x; X y(static_cast<X&&>(x)); // should bind to X(X const&) and not // generate an error? So technically the rule doesn't need to be lifted if we get rvalue references. -- Daniel Wallin

David Abrahams wrote:
Yeah it would be best if the rule could be lifted, but it isn't really *that* bad: template<class T, class U> struct enable_only_move; template<class T> struct enable_only_move<T, const T> { typedef typename T::error type; }; template<class T> struct enable_only_move<T, T> { typedef int type; }; // only movable class X { public: X() {} X(X const&) {} X& operator=(X) {} private: template<class U> X(U&, typename enable_only_move<X, U>::type = 0); }; -- Daniel Wallin

Dave A: Just out of curiosity, how many compilers like your technique? Daniel Wallin: I have only been skimming this thread. Could you comment on its relevance to the move stuff you sent me earlier? In particular, does this thread suggest any changes to the code you sent me? Also, I tested your code with gcc-3.3, and it mostly liked it. I believe that it likes the moving part, but it triggers a bizarre error on an unrelated test that I think is a bug in 3.3 (due to improper instantiations during overload resolution...at least, I hope that's all it is, because I'm upgrading to 3.3.2 right now, and that fixes a bug of that nature). Also, do you expect your technique to work properly for the conversion copy c'tor case: template <typename U, policies...> smart_ptr(smart_ptr<U, policies> const&p); template <typename U, policies...> smart_ptr(smart_ptr<U, policies>&p); These two c'tors are still in the code. Do you think that will interfere with the move semantics when doing a move-convert? i.e.: smart_ptr<Base, destructive_copy> p( smart_ptr<Derived, destructive_copy>(new Derived) ); If it does, what do you suggest we do? Simply removing the non-const& c'tor is undesirable because it limits what policies can do. Dave --- Outgoing mail is certified Virus Free. Checked by AVG anti-virus system (http://www.grisoft.com). Version: 6.0.571 / Virus Database: 361 - Release Date: 1/26/2004

"David B. Held" <dheld@codelogicconsulting.com> writes:
Dave A: Just out of curiosity, how many compilers like your technique?
Really, _really_ like it? AFAIK just GCC 3.3.1 (since EDG errors in strict mode) but I've only tested about 4 compilers on this. -- Dave Abrahams Boost Consulting www.boost-consulting.com

David B. Held wrote:
I don't think so. The code was mostly Rani's auto_ptr trick + some changes in the constructors to allow policies to enforce move-only.
No it should work as expected. This specialization forbids construction from lvalues, conversions too. template<class U, BOOST_CONVERSION_PARAMETERS> struct cant_move_from_const< const smart_ptr<U, BOOST_CONVERSION_POLICIES>
;
-- Daniel Wallin

"Daniel Wallin" <dalwan01@student.umu.se> wrote in message news:4028DF54.2020303@student.umu.se...
I don't see how. This only gets "activated" during a call to: template <typename P> smart_ptr(P&, cant_move...<>); But, in this case: smart_ptr<Base, destructive_copy> p( smart_ptr<Derived, destructive_copy>(new Derived) ); The better match is not smart_ptr(P&) but rather smart_ptr(smart_ptr<U>&), because the latter is more specialized. This seems to totally circumvent your system. Does the conversion c'tor also need to use the cant_move device? I.e.: template <typename U, policies...> smart_ptr(smart_ptr<U, policies>&p, cant_move..); Also, I can't use these changes at all anyway right now, because even gcc 3.3.2 doesn't like it. It likes it fine if you only have move pointers. But for some reason, it instantiates the move policies during tests with non-move configurations, which I'm pretty sure is a bug in gcc that I don't have time to isolate and report right now. And if the latest gcc doesn't like it, I have little hope for other compilers. If you are interested in trying to diagnose the problem yourself, I will try to update the sandbox with my working version. Dave --- Outgoing mail is certified Virus Free. Checked by AVG anti-virus system (http://www.grisoft.com). Version: 6.0.581 / Virus Database: 368 - Release Date: 2/9/2004

David B. Held wrote:
No, it doesn't need to be the best match. The error is generated when the overload is considered during overload resolution. Whenever U is substituted by smart_ptr<U, ...> const the compilation fails.
<snip> If you are interested in trying to diagnose the problem yourself, I will try to update the sandbox with my working version.
Yes I can take a look at it. I only have access to gcc3.3.1 and and 2.95.3 though. -- Daniel Wallin

On Feb 9, 2004, at 9:44 AM, David Abrahams wrote:
I'm not sure I'm completely following, but let me try... Given: class A { public: A(); A(A&&); ~A(); private: A(const A&); A& operator=(const A&); }; (or perhaps more elegantly: explicit class A { public: A(); A(A&&); ~A(); }; :-) ) and void foo(const A&); then the problem is: foo(A()); ? Problem because 12.1 requires an accessible copy ctor even if the temp is elided? I don't think this is a problem with the move proposal (of course 12.1 would probably need to be clarified with the introduction of the rvalue reference). Reasoning: foo(A()); expands to: foo(A(A())); i.e. a temporary copy is at least conceptually created and then that is bound to the const A&. In order to create the temporary copy, name lookup finds two possible signatures to create the copy: A(A&&); A(const A&); Since the argument is an rvalue, the accessible A(A&&) is chosen over the inaccessible A(const A&), and so I see no problem. Now the extra temporary can be elided, and in that case the original temporary is bound to foo's parameter. Or, if the extra temporary is not elided, then the original temporary is used to move construct another temporary which in turn is bound to foo's parameter. Either way, the result is the same (except for efficiency issues of move constructing an extra temporary), and the process is consistent with our existing process. Or have I completely missed your point? -Howard

Peter Dimov wrote:
However (apologies for the follow up to self) the rest of Howard's analysis still seems to apply, i.e. the CA temporary is created with A::A(A&&), and the A const & parameter of foo is bound directly to that temporary (if not elided).

On Feb 9, 2004, at 11:33 AM, Peter Dimov wrote:
That's not the first time I'm skimmed over one of Rani's posts too quickly and lived to regret it. Thanks for the whap across the nose! :-) <mental note, if Rani writes it, read at least 3 times and not in a hurry>
And thanks for the corrected analysis. Aside: I'm sure there are dark corners of the standard that would need to be closely inspected and perhaps adjusted for the existence of the proposed rvalue reference. Although I'd be thrilled to help, I'm a library expert, not a core expert. The core experts will have to lead on the move proposal if it is to succeed. Once core delivers the fundamental tool, I know what I want to do with it in the std::lib. Further aside: I'm impressed by the cleverness of the C++98-compatible (or nearly so) move techniques that have surfaced (Mojo, enable_if-based, and even the old auto_ptr_ref solution). But one of my overriding goals in the language-based move proposal was that move logic must be dirt simple to code because Joe Coder needs and wants this functionality in his own classes: class Employee { public: Employee(const std::string& name) // copy a name into the class : name_(name) {} Employee(std::string&& name) // move a name into the class (optimization) : name_(move(name)) {} ... Employee(Employee&& e) // move an Employee : name_(move(e.name_)) ... {} // compiler generated copy ctor ok ... private: std::string name_; ... }; Dealing explicitly with auxilary auto_ptr_ref-like classes when either using move, or when creating movable (and perhaps also copyable) classes was recognized as a possibility (just due to the existence of auto_ptr) but rejected as too complicated and tedious for general use. -Howard

Howard Hinnant <hinnant@twcny.rr.com> writes:
I don't know. If that analysis were correct, then Comeau would give the same error for this snippet in my example. typedef X const XC; sink2(XC(X())); but instead I see: ------ test 20, direct initialize a const A with an A ------- X() #24 MOVE #25 <== #24 in const lvalue sink2 destroy #25 destroy #24 (EMPTY)
<snip> That's what I meant when I mentioned this was not the cleanest approach.
Not to mention that my technique (and MoJo) require duplicating the copy ctor. -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
That's because your "move" constructor is not a move constructor from language point of view (i.e. it has no special status). As I understand it, the compiler's thought process goes somewhat like: need to create a temporary, but it can be elided, so we'll just access check the copy constructor. The compiler doesn't want to backtrack and try not to elide the temporary, which would've succeeded. X::X(X&&), if it existed and operated like we proposed, would enjoy the same special status as the copy constructor, and the compiler would know that it can be elided as it creates an object that is equivalent to the old value of the argument.

"Peter Dimov" <pdimov@mmltd.net> writes:
OK, so 8.5.3/5 says: A temporary of type ``cv1 T2'' [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.* [Footnote: Clearly, if the reference initialization being processed is one for the first argument of a copy constructor call, an implementation must eventually choose the first alternative (binding without copying) to avoid infinite recursion. --- end foonote] The constructor that would be used to make the copy shall be callable whether or not the copy is actually done. The problem here is, IIUC, that the implementors read that as saying that, because "a constructor is called to copy..." that the constructor used must be a copy constructor? I don't understand why the converting constructor fails to be considered when the templated SFINAE'd constructor *will* be used to copy const lvalues. Templated constructors are never "copy ctors" either, IIUC. -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
It's a fine specimen of circular logic. The compiler is allowed to elide a temporary created by a copy constructor. The compiler is _not_ allowed to elide a temporary created by some other constructor, because this changes observable behavior and is not explicitly allowed. The only copy constructor that can create a const temporary from an rvalue is X::X(X const &) (default arguments and ellipsis aside.) Therefore, once the compiler has decided to elide the temporary, it only needs to check for X(X const &). If such a constructor does not exist or is inaccessible, the elision of the temporary is illegal. This behavior is perfectly safe and reasonable, _provided that_ the auto_ptr loophole does not exist. It was never intended to exist. Everything that is based on it goes against the core logic of the language. Does it make sense? Once we are on the topic. As I understand it, 8.5.3 is intended to allow compilers to place ROM-able temporaries in ROM. Consider: void f(int const & x); int main() { f(5); } The compiler can place a temporary of type "int const" and value 5 in ROM, and pass its address to f. Similarly, in: struct X { int i; X(int i): i(i) {} }; void f(X const & x); int main() { f( X(5) ); } the compiler is allowed to construct a "X const" with a value of X(5) at compile time and place it in ROM.

"Peter Dimov" <pdimov@mmltd.net> writes:
I think what's explicitly allowed depends on your reading of 8.5.3/5
Well, I understand that as a philosophy, but I don't see it in the standard. I also don't see any circle ;-)
I guess so. I still don't know what the loophole is. I knew once, but that knowledge is ghostly and flits away quickly.
OK. Relevance? -- Dave Abrahams Boost Consulting www.boost-consulting.com

"Peter Dimov" <pdimov@mmltd.net> writes:
1. Do you realize that 12.8/15 was almost completely rewritten by Core issue 20? 2. I still think it's arguable that 8.5.3/5 does not require a copy constructor to be used in my example, making 12.8/15 irrelevant AFAICT. -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
The auto_ptr loophole enables two user defined conversions in a row, something that is usually forbidden. The reason, as I understand it, is that when the source and target types are the same, parts of the standard are written under the unspoken assumption that a copy constructor will perform the copy; however, other parts (past the "check for two UDC" point) simply apply the usual overload resolution rules, choosing the X -> ref -> X sequence due to the copy constructor being nonviable.
I seem to recall attempts to strike down that second bullet of the first bullet of the second bullet of 8.3.5/5 :-) and demand that the reference be bound to the object represented by the rvalue. At the time, this seemed a reasonable idea, but then I came up with the ROM example. Maybe it's already widely known and I'm just reinventing the wheel.

"Peter Dimov" <pdimov@mmltd.net> writes:
BTW, what does the standard actually say about the const-ness of introduced temporaries? I'd always assumed that they would be non-const in the same way as rvalues of class type, but I've obviously missed something. -- Raoul Gough. export LESS='-X'

David Abrahams wrote:
(As I already mentioned) http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/cwg_active.html#291: <Q> Another detail worth of note is that in the *draft* version of the Standard as of 2 December 1996 the second bullet read: A temporary of type "cv1 T2" [sic] is created, and a *copy constructor* is called to copy the entire rvalue object into the temporary... J. Stephen Adamczyk replied that the reason for changing "a copy constructor" to "a constructor" was to allow for member template converting constructors. </Q> This relaxation makes any viable converting constructor legitimate for constructing this temporary and therefore the syntactic constraint is met which makes your (and Andrei's) suggestion perfectly functional in the boundaries of the current standard. Regards, Rani

David Abrahams <dave@boost-consulting.com> writes: [snip]
I seem to remember a supposed useful purpose in cases where small objects can be passed in registers. I can never quite convince myself one way or the other on this point, but it seems like giving up language power in favour of the a hypothetical performance optimization. I'm sure this rule confounded some move-semantics proposal from Andrei Alexandrescu as well.
*If* my example actually works other than this case, I'd like to try to get the rule lifted, and soon!
Isn't 8.5.3 the really hard part? I once discussed this in some depth with Rani Sharoni, and ended up posting a proposal to comp.std.c++ about relaxing the copy constructor requirement for const& function arguments. Let's just say that it wasn't met with much interest! The argument was basically that the compiler can be forced *not* to copy the rvalue anyway, so there can't be a fundamental reason to require an accessible copy constructor. e.g. struct A { A &as_lvalue () { return *this; } }; void foo (A const &); void bar () { foo (A()); // Can copy foo (A().as_lvalue()); // Can't copy } I'd still like to see the requirement disappear (many compilers don't enforce it anyway, and never actually create the temporary). The other reference binding case is somewhat trickier, of course: void bar() { A const &temp1 = A(); // Can copy A const &temp2 = A().as_lvalue(); // Dangling reference! } -- Raoul Gough. export LESS='-X'

Raoul Gough wrote:
Lately we discovered that there is difference between #1 and #2 since #1 might produce a const object, since in case that additional temporary is introduced it is const, with all the following consequences (e.g. potential const optimizations that no compiler actually do). Rani

"Rani Sharoni" <rani_sharoni@hotmail.com> writes:
The thing I've never understood about the copyable requirement is that I don't see what it gains for me, the programmer. OK, maybe if the compiler is allowed to introduce a temporary, it might be able to make the code run faster in some cases. I'll believe that if and when I ever see it myself. Surely a compiler that is smart enough to do that could decide to do it anyway with an "as-if" justification. It could also decide not to do it if the copy constructor isn't available (and surely it would have to be trivial or at least inline if it supposed to gain performance somehow). Anyway, for many if not most UDTs, calling the copy constructor is going to reduce performance significantly. I wonder if potential optimizations were ever the real reason for including this requirement? Maybe it just makes a compiler easier to implement, but that would be a poor trade off, IMHO. -- Raoul Gough. export LESS='-X'

On Sat, 07 Feb 2004 01:00:49 +0100, David Abrahams wrote:
Nice. No, more than that, it's great! FWIW, I discovered that you can reduce the number of temporaries/objects further. In source() and csource(), change 'return X();' to 'X nrv; return nrv;'. Although I have to admit that I'm at a loss why the NRVO works while the RVO seems to be broken for your code (tested on Intel 7.1 and the GCC 3.3.2). Regards, Daniel

David Abrahams wrote:
I already mentioned during the improved auto_ptr discussions that I'm not smart enough to use the new trick in order to introduce move semantics. It seems that you amazingly did it!
The problem with reference binding using conversion operator/constructor(i.e. X const& r = X()) is addressed in active DR #291 (see Mr. Adamczyk note) and AFAICT it was discussed during the MoJo days. IMHO the current standard explicitly allows it (per 8.5.3/5/2/1) and EDG is not very consistent about it: auto_ptr<Base> const& r = auto_ptr<Derived>(); // accepted by EDG auto_ptr<Base> const& r = auto_ptr<Base>(); // rejected by EDG Thank you, Rani

"Rani Sharoni" <rani_sharoni@hotmail.com> writes:
It was indeed inspired by your auto_ptr implementation.
I'm not really sure what to make of that text.
and AFAICT it was discussed during the MoJo days.
Yep. I knew about it then, but it's so easy forgotten when you're hunting for the elusive "move"
I believe that those two cases use different rules. In the first case you have an implicit conversion, while in the second case you only have copy initialization. -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
I'll try to elaborate on that. 8.3.5/5 states that when binding class r-value to a const reference there is a possibility to introduce additional const temporary from the r-value and then bind it to the reference (which might, recursively, introduce, yet additional temporary). The question is how to construct this additional const temporary which, according to TC1 #171, is an r-value. Now we are back to Steve Adamczyk comment in DR #291 (and http://tinyurl.com/2kdtk). According to that comment the copy constructor requirement was explicitly removed to allow template converting constructors which is *not* copy-constructor. This relaxation also (accidentally?) allows other converting constructors since they are as good as template converting constructor in context of copy initialization from the same type. This means that X const& r = X() might produce the following code: 1) Construct additional const temporary using X::X(ref) which requires call to the conversion operator. This is allowed since the initialization is from the same type. 2) Repeat X >= 0 times: construct additional const temporary from the const r-value. 3) Bind the final r-value to the reference The fact that the additional temporary is const might, theoretically, encourage the optimizer to generate faster code but AFAIK no C++ optimizer is actually aware about the existence of C++ const and *ignore* it. Anyway, we don't really care about this elided coping constraint since if compiler actually makes additional coping then it probably has very good reason: X const& r = true ? X() : X(); // VC and GCC introduce additional r-value using the "r-value" move constructor.
I think that MoJo also achived the "move" goal but your solution is more elegant.
I just presented this case to show that even EDG has problems with initialization. The first case is obviously forbidden by TC1 #84, which most of the times EDG and all compilers respects. The second case is our case and it is, IMO, explicitly allowed. Rani

"Rani Sharoni" <rani_sharoni@hotmail.com> writes: [snip]
So I can't cast away const on the reference, because it might be bound to a const copy of the rvalue (leading to undefined behaviour)? BTW, couldn't the optimizer (after verifying that the const & is never const-casted) perform the same optimizations anyway?
The funny thing about the conditional expression is that neither VC++ 7.1 nor g++ 3.3.1 can compile the case with an rvalue-to-lvalue conversion in the following code: class nc { nc (nc const &) { } public: nc () { } nc const &by_ref () const { return *this; } }; struct nc_derived : nc { }; void bar (nc const &); void foo (bool b) { bar (b ? nc() : nc_derived()); // Ill-formed (8.5.3) bar ((b ? nc() : nc_derived()).by_ref()); // OK AFAIK } Both VC++ and g++ report that the nc copy constructor is inaccessible on both of the bar calls. I don't see how the compiler is allowed to introduce a copy in the second one (with the member function call). Comeau online 4.3.3 diagnoses the error on the ill-formed line and accepts the second one. -- Raoul Gough. export LESS='-X'

Raoul Gough wrote:
That's right. You can work around this theoretical pitfall using mutable members if needed.
BTW, couldn't the optimizer (after verifying that the const & is never const-casted) perform the same optimizations anyway?
I had aliasing optimizations in mind: struct X { bool flag; }; X source(); void DoSomething(X const&); void foo() { X const& r = source(); if (r.flag) { /* #1 */ } DoSomething(r); if (r.flag) { /* #2 */ } } In case that the optimizer is C++ const aware then it can consider the temporary as being const (after wisely eliminating the additional fake copy to const temporary). Now since r refers to const object the optimizer is free to think that the value of it will not change after calling DoSomething (i.e. potential aliasing issue). Such aliasing issues might seem insignificant yet AFAIK this is what makes FORTRAN much faster than C (i.e. assuming no aliasing).
This is dark C++ corner: http://tinyurl.com/2p83e Rani

David Abrahams wrote:
VC 7.1 fails to compile test 15:
std::cout << " ------ test 15, pass const lvalue by-value ------- " << std::endl; sink(z7);
error C2664: 'sink' : cannot convert parameter 1 from 'const X' to 'X' No copy constructor available for struct 'X' or constructor attempts to perform illegal conversion to non-__gc reference IMFO, this is a compiler defect - I'll report it to Microsoft. br, Stefan ---------------------------------------------- Stefan Slapeta Vienna / Austria http://www.slapeta.com
participants (14)
-
Anthony Williams
-
Brian McNamara
-
Christoph Ludwig
-
Daniel Frey
-
Daniel Frey
-
Daniel Wallin
-
David Abrahams
-
David B. Held
-
Howard Hinnant
-
Mat Marcus
-
Peter Dimov
-
Rani Sharoni
-
Raoul Gough
-
Stefan Slapeta