Number: | N1610=04-0050 |
---|---|
Author: | David Abrahams, Gary Powell |
Contact: | dave@boost-consulting.com, powellg@amazon.com |
Organization: | Boost Consulting, Amazon.com |
Date: | February 14, 2004 |
Project: | Programming Language C++, Evolution Working Group |
Abstract: | The interpretation of one paragraph of the C++ standard, 8.5.3 paragraph 5, has the power:
This paper proposes to clarify 8.5.3/5 so that it unambiguously allows move semantics and allows the transformation work uniformly. |
---|
Unnecessary temporaries and the cost of copying them are a serious impediment to writing clear, concise and efficient code in C++. The desire to avoid temporary copies of large objects results in users writing code like:
a = b; a += c;
where the equivalent, but more-expressive
a = b + c;
is intended. In the absence of expression template heroics, the expressive code computes the new value of a, then copies it and throws the original copy away. Programmers have learned to use in-place modification (operator+=) to avoid the final wasteful copy.
The language feature needed to make the expressive code efficient is called "move semantics" [n1377]. Move semantics relies on being able to "steal" the resources held by rvalues when those rvalues are copied. The rvalues are about to disappear anyway, so it makes no sense to copy their internals -- such copying can incur a great cost as values are passed across function boundaries. For example, when a function returns a vector<T>, every element in the vector must be copied just before the original vector is destroyed. Allowed return value optimizations can help in some cases, but compilers are not always allowed to elide the copy construction. If the rvalue is copy-assigned into an existing variable, an un-elidable copy is performed. When rvalues are passed by value as function arguments, there are no allowed optimizations that can elide a copy construction.
Although a core language extension as proposed in n1377 could make "movability" easier and cleaner for users to implement, move semantics can be implemented within the current language if 8.5.3 paragraph 5 is interpreted favorably. Many implementations, however, interpret 8.5.3/5 unfavorably.
While this paper will focus on move semantics, the interpretation of 8.5.3/5 has another important effect: an unfavorable interpretation means that the accepted optimization of passing a parameter by const& instead of by value is not generically applicable.
To decide whether resources may be transferred away from a constructor argument, we need to be able to distinguish whether it is an lvalue or an rvalue. Without a language extension as described in n1377, it's impossible to distinguish an rvalue argument from an lvalue argument from inside a constructor, so we must do it outside, by directing rvalues and lvalues to different overloads.
The example below shows an implementation of a movable class X. Here we'll walk through its implementation.
Traditional copy ctors with const reference parameters
X(X const& rhs);
will swallow all rvalues and const lvalues, so we cannot use one of those if we want to distinguish rvalues from lvalues.
A copy ctor with a mutable reference parameter will swallow only non-const lvalues, so we use one of those:
X(X& rhs);
Rvalues will never bind to a T& parameter, even if T is a template parameter, but const lvalues will (with T deduced as U const for some U), so we use a templated ctor to capture const lvalues. SFINAE (14.8.3) is used to ensure that the templated ctor is only considered when T == X:
template <class T, class X> struct enable_if_same { }; template <class X> struct enable_if_same<X, X> { typedef char type; }; struct X { ... // const lvalue - T will be deduced as X const template <class T> X(T& rhs, typename enable_if_same<X const,T>::type = 0)
[The Mojo example in the appendinx uses a different technique for handling const lvalues.]
To handle rvalues we rely on the backdoor used by auto_ptr, which allows a user-defined conversion to be used in matching a converting ctor's argument. A conversion operator to the nested ref class and a ctor taking a "ref" object will be used in the case where the argument is an rvalue:
struct ref { ref(X*p) : p(p) {} X* p; }; operator ref() { return ref(this); } // non-const rvalue constructor - steals resources. X(ref rhs);
Constant rvalues are should never have their resources stolen. Since the operator ref() conversion is non-const, that can never happen.
X's only copy-assignment operator takes an X by value:
X& operator=(X rhs);
inside operator=, we are free to move resources out of rhs and into *this: if the argument was an lvalue, it will have been copied into rhs. If the argument was an rvalue, its resources will have been moved into rhs. In fact, any function that needs to steal resources from a copy of its argument, can safely be written to accept the argument by value, and rvalues of movable types won't be copied.
8.5.3/5 says that there must be an accessible constructor that could be used to copy any temporary bound to a const reference. It does not say that this accessible ctor must be a copy ctor, yet many implementations interpret that passage as though it says there must must be an accessible copy ctor with a const& argument.
Enforcement of this interpretation is usually inconsistent. For example, all accept:
X const& r = X();
while some compilers reject:
extern sinkref(X const&); sinkref(X()); // error - no accessible copy ctor
In the example below, many compilers fail to compile test 10 because of the way 8.5.3/5 is interpreted, while at the same time,
typedef X const XC; XC(X());
does compile. In other words, it is possible to construct the const temporary from the rvalue. We think that's the proper criterion to use in determining test 10's legailty. 8.5.3/5 doesn't demand that a "copy constructor" is used to copy the temporary, only that a constructor is used "to copy the temporary".
It appears, looking at core issues 291/391, that there may be a need to tighten 8.5.3/5. When the language is fixed to specify direct (or copy initialization), it should also unambiguously allow the enclosed test to compile. Not only is it within the scope of reasonable interpretation of the current standard, but it's an incredibly important piece of functionality for users.
Note also that, with an unfavorable interpretation, 8.5.3/5 means that rvalues of classes like X (and std::auto_ptr<T>) cannot be passed where a const& parameter is expected, even though they can be passed by value. Move semantics aside, this quirky non-uniformity can make writing generic code extremely difficult.
This example shows the implementation of a class X supporting move semantics. An earlier approach by Andrei Alexandrescu is included as an appendix.
// move.cpp // Author : David Abrahams // Uses SFINAE to get the compiler to pick the correct constructor. #include <iostream> #include <cassert> template <class T, class X> struct enable_if_same { }; template <class X> struct enable_if_same<X, X> { typedef char type; }; struct X { static int cnt; // count the number of Xs X() : id(++cnt) , owner(true) { std::cout << "X() #" << id << std::endl; } // non-const lvalue - copy ctor X(X& rhs) : id(++cnt) , owner(true) { std::cout << "copy #" << id << " <- #" << rhs.id << std::endl; } // const lvalue - T will be deduced as X const template <class T> X(T& rhs, typename enable_if_same<X const,T>::type = 0) : id(++cnt) , owner(true) { std::cout << "copy #" << id << " <- #" << rhs.id << " (const)" << std::endl; } ~X() { std::cout << "destroy #" << id << (owner?"":" (EMPTY)") << std::endl; } X& operator=(X rhs) { std::cout << "ASSIGN #" << id << (owner?"":" (EMPTY)") << " <== #" << rhs.id << (rhs.owner?"":" (EMPTY)") << std::endl; owner = rhs.owner; rhs.owner = false; assert(owner); } private: // Move stuff struct ref { ref(X*p) : p(p) {} X* p; }; public: // Move stuff operator ref() { return ref(this); } // non-const rvalue X(ref rhs) : id(++cnt) , owner(rhs.p->owner) { std::cout << "MOVE #" << id << " <== #" << rhs.p->id << std::endl; rhs.p->owner = false; assert(owner); } private: // Data members int id; bool owner; }; int X::cnt; X source() { return X(); } X const csource() { return X(); } void sink(X) { std::cout << "in rvalue sink" << std::endl; } void sink2(X&) { std::cout << "in non-const lvalue sink2" << std::endl; } void sink2(X const&) { std::cout << "in const lvalue sink2" << std::endl; } void sink3(X&) { std::cout << "in non-const lvalue sink3" << std::endl; } template <class T> void tsink(T) { std::cout << "in templated rvalue sink" << std::endl; } int main() { std::cout << " ------ test 1, direct init from rvalue ------- " << std::endl; #ifdef __GNUC__ // GCC having trouble parsing the extra parens X z2((0, X() )); #else X z2((X())); #endif std::cout << " ------ test 2, copy init from rvalue ------- " << std::endl; X z4 = X(); std::cout << " ------ test 3, copy init from lvalue ------- " << std::endl; X z5 = z4; std::cout << " ------ test 4, direct init from lvalue ------- " << std::endl; X z6(z4); std::cout << " ------ test 5, construct const ------- " << std::endl; X const z7; std::cout << " ------ test 6, copy init from lvalue ------- " << std::endl; X z8 = z7; std::cout << " ------ test 7, direct init from lvalue ------- " << std::endl; X z9(z7); std::cout << " ------ test 8, pass rvalue by-value ------- " << std::endl; sink(source()); std::cout << " ------ test 9, pass const rvalue by-value ------- " << std::endl; sink(csource()); std::cout << " ------ test 10, pass rvalue by overloaded reference ------- " << std::endl; // This one fails in Comeau's strict mode due to 8.5.3/5. GCC 3.3.1 passes it. sink2(source()); std::cout << " ------ test 11, pass const rvalue by overloaded reference ------- " << std::endl; sink2(csource()); #if 0 // These two correctly fail to compile, just as desired std::cout << " ------ test 12, pass rvalue by non-const reference ------- " << std::endl; sink3(source()); std::cout << " ------ test 13, pass const rvalue by non-const reference ------- " << std::endl; sink3(csource()); #endif std::cout << " ------ test 14, pass lvalue by-value ------- " << std::endl; sink(z5); std::cout << " ------ test 15, pass const lvalue by-value ------- " << std::endl; sink(z7); std::cout << " ------ test 16, pass lvalue by-reference ------- " << std::endl; sink2(z4); std::cout << " ------ test 17, pass const lvalue by const reference ------- " << std::endl; sink2(z7); std::cout << " ------ test 18, pass const lvalue by-reference ------- " << std::endl; #if 0 // correctly fails to compile, just as desired sink3(z7); #endif std::cout << " ------ test 19, pass rvalue by value to template param ------- " << std::endl; tsink(source()); std::cout << " ------ test 20, direct initialize a const A with an A ------- " << std::endl; typedef X const XC; sink2(XC(X())); std::cout << " ------ test 21, assign from lvalue ------- " << std::endl; z4 = z5; std::cout << " ------ test 22, assign from rvalue ------- " << std::endl; z4 = source(); }
We believe that, strictly speaking, no extensions to C++98 are needed but that a general clarification of 8.5.3/5 would be helpful.
Suggested change to 8.5.3/5 is from:
-- 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. 93) The constructor that would be used to make the copy shall be callable whether or not the copy is actually done.
To:
-- A temporary of type "cv/T2"[sic] is created via direct initialization from the entire rvalue object. The reference is bound to the temporary or to a sub-object within the temporary. 93) The functions that would be used in the initialization shall be callable whether or not the temporary is actually created.
N1610=04-0050 is the first version.
Discussions among David Abrahams, Andrei Alexandrescu, and Evgeny Karpov has renewed the interest to have either the std text modified or clarified so that all C++03 implementations are consistent with their treatment of initialization of classes from rvalues.
[n1377] | A Proposal to Add Move Semantics Support to the C++ September 10, 2002 http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1377.htm Howard E. Hinnant, hinnant@twcny.rr.com , Peter Dimov, pdimov@mmltd.net, Dave Abrahams , dave@boost-consulting.com |
[Mojo] | Andrei Alexandrescu, Mojo C/C++ User’s Journal, February 2003, |
// Mojo uses a unique type to get the compiler to pick the right constructor. ////////////////////////////////////////////////////////////////////////////// // MOJO: Moving Joint Objects // Created by Andrei Alexandrescu // // Permission to use, copy, modify, distribute and sell this software for any // purpose is hereby granted without fee, provided that the above copyright // notice appear in all copies and that both that copyright notice and this // permission notice appear in supporting documentation. // The author makes no representations about the suitability of this software // for any purpose. It is provided "as is" // without express or implied warranty. // // Change Log: // 28 Oct 2002 Gary Powell added operator-> to constant and temporary and // fnresult. Also templated conversion operators to // mojo::enabled. //////////////////////////////////////////////////////////////////////////////// #ifndef MOJO_H_ #define MOJO_H_ namespace mojo { template <class T> class constant // type sugar for constants { public: const T* data_; explicit constant(const T& obj) : data_(&obj) {} template<class S> constant(constant<S> const &rhs) : data_(rhs.data_) {} const T& get() const { return *data_; } T const *operator->() const { return data_; } private: constant &operator=(constant const&); }; // forward declaration. template<class T> class fnresult; template <class T> class temporary : private constant<T> // type sugar for temporaries { template<class S> friend class temporary; public: explicit temporary(T& obj) : constant<T>(obj) {} template<class S> temporary(temporary<S> &rhs) : constant<T>(rhs) {} template<class S> temporary(fnresult<S> const &rhs) : constant<T>(rhs.get()) {} T& get() const { return const_cast<T&>(constant<T>::get()); } T const *operator->() const { return constant<T>::operator->(); } T *operator->() { return const_cast<T *>(constant<T>::operator->() ); } }; template <class T> class fnresult { T m_T; public: explicit fnresult(T& rhs) : m_T(temporary<T>(rhs)) {} // The cast's below is valid given that nobody ever really creates a // const fnresult object fnresult(const fnresult& rhs) : m_T(temporary<T>(const_cast<fnresult&>(rhs))) {} template<class S> fnresult(const fnresult<S> &rhs) : m_T(temporary<T>(const_cast<fnresult<S>&>(rhs))) {} T& get() { return const_cast<T &>(m_T); } T const & get() const { return m_T; } T const *operator->() const { return &m_T; } T *operator->() { return &m_T; } private: fnresult &operator=(fnresult const &); }; template <class T> struct enabled { operator temporary<T>() { return temporary<T>(static_cast<T&>(*this)); } template<class S> operator temporary<S>() { return temporary<S>(static_cast<S&>(*this)); } operator constant<T>() const { return constant<T>(static_cast<const T&>(*this)); } template<class S> operator constant<S>() const { return constant<S>(static_cast<const S&>(*this)); } operator fnresult<T>() { return fnresult<T>(static_cast<T&>(*this)); } template<class S> operator fnresult<S>() { return fnresult<S>(static_cast<S&>(*this)); } protected: enabled() {} // intended to be derived from }; } #endif // MOJO_H_ // ------------------- testpointer.h -------- #if !defined(_TESTPTR_H) #define _TESTPTR_H #include <iostream> #include <string> typedef enum PtrStates { A_PTR = 0xFFFF, DESTROYED_PTR = 0xDCDC, UNINITED_PTR = 0xDEADC0DE, ZERO = 0}; // class to watch for misuse of the pointers class TestPtr { PtrStates m_ptr; const ::std::string m_className; public: TestPtr() : m_ptr(UNINITED_PTR) , m_className("UnIdentified") {} TestPtr(::std::string const &name) : m_ptr(UNINITED_PTR) , m_className(name) {} TestPtr(PtrStates rhs, ::std::string const &name) : m_ptr(rhs) , m_className(name) {} TestPtr(TestPtr const &rhs) : m_ptr(rhs.m_ptr) , m_className(rhs.m_className) { using ::std::cout; if (m_ptr == UNINITED_PTR) { cout << __FILE__ << '[' << __LINE__ << ']' << "WARNING CONSTRUCTIONG unitialized ptr from " << m_className << " unitialized ptr\n"; } else if (m_ptr == ZERO) { cout << __FILE__ << '[' << __LINE__ << ']' << "WARNING! initialized from " << m_className << " dead temporary.\n"; } else if (m_ptr == DESTROYED_PTR) { cout << __FILE__ << '[' << __LINE__ << ']' << "ERROR! initialized from " << m_className << " deleted value!\n"; } } ~TestPtr() { using ::std::cout; if (m_ptr == UNINITED_PTR) { cout << __FILE__ << '[' << __LINE__ << ']' << "WARNING! DELETEING unitialized " << m_className << "ptr at " << this << '\n'; } else if (m_ptr == DESTROYED_PTR) { cout << __FILE__ << '[' << __LINE__ << ']' << "ERROR Double delete of " << m_className << " ptr at " << this << '\n'; } m_ptr = DESTROYED_PTR; } TestPtr &operator=(TestPtr const &rhs) { using ::std::cout; m_ptr = rhs.m_ptr; if (m_ptr == UNINITED_PTR) { cout << __FILE__ << '[' << __LINE__ << ']' << "WARNING assigning from " << m_className << " const unitialized ptr\n"; } else if (m_ptr == ZERO) { cout << __FILE__ << '[' << __LINE__ << ']' << "WARNING! assigning from " << m_className << " const dead temporary.\n"; } else if (m_ptr == DESTROYED_PTR) { cout << __FILE__ << '[' << __LINE__ << ']' << "ERROR! assigning from " << m_className << " const deleted value!\n"; } return *this; } TestPtr &operator=(PtrStates rhs) { using ::std::cout; m_ptr = rhs; if (m_ptr == UNINITED_PTR) { cout << __FILE__ << '[' << __LINE__ << ']' << "WARNING assigning from " << m_className << " unitialized ptr\n"; } else if (m_ptr == ZERO) { cout << __FILE__ << '[' << __LINE__ << ']' << "WARNING! assigning from " << m_className << " dead temporary.\n"; } else if (m_ptr == DESTROYED_PTR) { cout << __FILE__ << '[' << __LINE__ << ']' << "ERROR! assigning from " << m_className << " deleted value!\n"; } return *this; } void reset() { m_ptr = ZERO; } void validate() const { using ::std::cout; if (m_ptr == UNINITED_PTR) { cout << __FILE__ << '[' << __LINE__ << ']' << "INVALID DATA" << m_className << " unitialized ptr\n"; } else if (m_ptr == ZERO) { cout << __FILE__ << '[' << __LINE__ << ']' << "INVALID DATA" << m_className << " dead temporary.\n"; } else if (m_ptr == DESTROYED_PTR) { cout << __FILE__ << '[' << __LINE__ << ']' << "INVALID DATA" << m_className << " deleted value!\n"; } } void swap(TestPtr &rhs) { PtrStates tmp(m_ptr); m_ptr = rhs.m_ptr; rhs = tmp; } }; #endif // ------------------ testclass.h ------------ #if !defined(_TESTCLASS_H) #define _TESTCLASS_H #include <algorithm> #include "testptr.h" #include "mojo.h" class Y : public ::mojo::enabled<Y> { TestPtr m_ptr; public: Y() // default constructor : m_ptr("Y") {} Y(PtrStates ptr) : m_ptr(ptr, "Y") {} Y(const Y& rhs) // source is a const value : m_ptr(rhs.m_ptr) {} Y(::mojo::temporary<Y> src) // source is a temporary : m_ptr(src->m_ptr) { src->release(); } Y(::mojo::fnresult<Y> src) // source is a fn result : m_ptr(src->m_ptr) { src->release(); } ~Y() {} void swap( Y&rhs) { using ::std::swap; swap(rhs.m_ptr, m_ptr); } Y &operator=(Y const & rhs) // source is a non const lvalue { using ::std::swap; Y tmp(rhs); tmp.swap(*this); return *this; } Y &operator=(::mojo::temporary<Y> src) // source is a temporary { src->m_ptr.swap(m_ptr); src->release(); return *this; } Y &operator=(::mojo::fnresult<Y> src) // source is a fn result { return operator=(::mojo::temporary<Y>(src)); } void validate() const { m_ptr.validate(); } private: void release() { m_ptr.reset(); } }; // test with binary operator + ::mojo::fnresult<Y> operator +(Y lhs, Y const &rhs) { return lhs; } class Z : public Y { TestPtr m_ptr; public: Z() // default constructor : m_ptr("Z") {} Z(PtrStates ptr) : Y(ptr) , m_ptr(ptr, "Z") {} Z(Z const& rhs) : Y(rhs)// source is a const value , m_ptr(rhs.m_ptr) {} Z(::mojo::temporary<Z> src) : Y(::mojo::temporary<Y>(src) ) , m_ptr(src->m_ptr) // source is a temporary { src->release(); } Z(::mojo::fnresult<Z> src) : Y(::mojo::temporary<Y>(src.get()) ) , m_ptr(src->m_ptr) // source is a fn result { src->release(); } ~Z() {} void swap(Z &rhs) { using ::std::swap; Y::swap(rhs); swap(rhs.m_ptr, m_ptr); } Z &operator=(Z const &rhs) // source is a non const lvalue { using ::std::swap; Z tmp(rhs); tmp.swap(*this); return *this; } Z &operator=(::mojo::temporary<Z> src) // source is a temporary { using ::std::swap; Y::operator=(::mojo::temporary<Y>(src) ); src->m_ptr.swap(m_ptr); src->release(); return *this; } Z &operator=(::mojo::fnresult<Z> src) // source is a fn result { return operator=(::mojo::temporary<Z>(src)); } void validate() const { Y::validate(); m_ptr.validate(); } private: void release() { m_ptr.reset(); } }; class W : public ::mojo::enabled<W> { TestPtr m_ptr; public: W() // default constructor : m_ptr("W") {} W(PtrStates ptr) : m_ptr(ptr, "W") {} W(::mojo::fnresult<W> src) // source is a temporary : m_ptr(src->m_ptr) { src->release(); } W(::mojo::temporary<W> src) // source is a temporary : m_ptr(src->m_ptr) { src->release(); } ~W() {} void swap( W&rhs) { using ::std::swap; swap(rhs.m_ptr, m_ptr); } W &operator=(::mojo::temporary<W> src) // source is a temporary { src->m_ptr.swap(m_ptr); src->release(); return *this; } W &operator=(::mojo::fnresult<W> src) // source is a fnresult temporary { return operator=(::mojo::temporary<W>(src) ); } void validate() const { m_ptr.validate(); } private: void release() { m_ptr.reset(); } W(const W& rhs); // No copy construction! W &operator=(W const & rhs); // No assignment }; // test with no data members. class X : public ::mojo::enabled<X> { public: X() // default constructor {} X(::mojo::fnresult<X> src) // source is a temporary { src->release(); } X(::mojo::temporary<X> src) // source is a temporary { src->release(); } ~X() {} void swap( X&rhs) {} X &operator=(::mojo::temporary<X> src) // source is a temporary { src->release(); return *this; } X &operator=(::mojo::fnresult<X> src) // source is a fnresult temporary { return operator=(::mojo::temporary<X>(src) ); } void validate() const {} private: void release() { using ::std::cout; } X(const X& rhs); // No copy construction! X &operator=(X const & rhs); // No assignment }; class V : virtual public Y, public ::mojo::enabled<V> { TestPtr m_ptr; public: V() // default constructor : m_ptr("V") {} V(PtrStates ptr) : Y(ptr) , m_ptr(ptr,"V") {} V(V const& rhs) : Y(rhs)// source is a const value , m_ptr(rhs.m_ptr) {} V(::mojo::temporary<V> src) : Y(::mojo::temporary<Y>(src) ) , m_ptr(src->m_ptr) // source is a temporary { src->release(); } V(::mojo::fnresult<V> src) : Y(::mojo::temporary<Y>(src.get())) , m_ptr(src->m_ptr) // source is a temporary { src->release(); } virtual ~V() {} void swap(V &rhs) { using ::std::swap; Y::swap(rhs); swap(rhs.m_ptr, m_ptr); } V &operator=(V const &rhs) // source is a non const lvalue { V tmp(rhs); tmp.swap(*this); return *this; } V &operator=(::mojo::temporary<V> src) // source is a temporary { Y::operator=(::mojo::temporary<Y>(src) ); src->m_ptr.swap(m_ptr); src->release(); return *this; } V &operator=(::mojo::fnresult<V> src) // source is a temporary { return operator=(::mojo::temporary<V>(src) ); } void validate() const { Y::validate(); m_ptr.validate(); } private: void release() { m_ptr.reset(); } }; class U : virtual public Y, virtual public V, public ::mojo::enabled<U> { TestPtr m_ptr; public: U() // default constructor : m_ptr("U") {} U(PtrStates ptr) // default constructor : Y(ptr) , V(ptr) , m_ptr(ptr, "U") {} U(U const& rhs) : Y(rhs)// source is a const value , V(rhs) , m_ptr(rhs.m_ptr) {} U(::mojo::temporary<U> src) : Y(::mojo::temporary<Y>(src) ) , V(::mojo::temporary<V>(src) ) , m_ptr(src->m_ptr) // source is a temporary { src->release(); } U(::mojo::fnresult<U> src) : Y(::mojo::temporary<Y>(src.get())) , V(::mojo::temporary<V>(src.get())) , m_ptr(src->m_ptr) // source is a temporary { src->release(); } virtual ~U() { } void swap(U &rhs) { //Y::swap(rhs); V::swap(rhs); rhs.m_ptr.swap(m_ptr); } U &operator=(U const &rhs) // source is a non const lvalue { using ::std::swap; U tmp(rhs); tmp.swap(*this); return *this; } U &operator=(::mojo::temporary<U> src) // source is a temporary { //Y::operator=(::mojo::temporary<Y>(src) ); V::operator=(::mojo::temporary<V>(src) ); src->m_ptr.swap(m_ptr); src->release(); return *this; } U &operator=(::mojo::fnresult<U> src) // source is a temporary { return operator=(::mojo::temporary<U>(src) ); } void validate() const { Y::validate(); V::validate(); m_ptr.validate(); } private: void release() { m_ptr.reset(); } }; #endif // ----------------- main.cpp ---------------- #include "mojo.h" #include <iostream> #include <vector> #include <cassert> #include "testclass.h" using namespace std; // stupid macro to print the line, and then execute it. #define DO(x) cout << (#x) << "\n"; x ; cout << '\n'; const Y MakeConstY() { DO(return Y(A_PTR)); } ::mojo::fnresult<Y> MakeY() { DO(Y x(A_PTR)); DO(return x); } void TakeConstY(const Y&) { } void TakeY(Y&) { } const Z MakeConstZ() { DO(return Z(A_PTR)); } ::mojo::fnresult<Z> MakeZ() { DO(Z x(A_PTR)); DO(return x); } void TakeConstZ( Z const & rhs) { } void TakeZ( Z & rhs) { } ::mojo::fnresult<W> MakeW() { DO(W x(A_PTR)); DO(return x); } ::mojo::fnresult<U> MakeU() { DO(U x(A_PTR)); DO(return x); } ::mojo::fnresult<V> MakeV() { DO(V x(A_PTR)); DO(return x); } void Discriminate(Y&) {} void Discriminate(mojo::temporary<Y>) {} void Discriminate(mojo::constant<Y>) {} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// int main() { DO(Y nonConstLValue(A_PTR)); DO(const Y constLValue(A_PTR)); DO(Y y1(constLValue)); DO(Y y2(nonConstLValue)); DO(Y y3(MakeConstY())); DO(Y y4(MakeY())); DO(y4.validate() ); //TakeConstY(Y()); //TakeConstY(MakeY()); //TakeY(Y()); //TakeY(MakeY()); { cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" << endl; DO(Discriminate(Y(A_PTR))); // calls Discriminate(mojo::temporary<Y>) DO(Discriminate(MakeY())); // calls Discriminate(mojo::temporary<Y>) DO(Discriminate(constLValue)); // calls Discriminate(mojo::constant<Y>) DO(Discriminate(nonConstLValue)); // calls Discriminate(Y&) cout << "<><><><><><><><><><><><><><><><><><><><>" << endl; } { cout << "=======================================" << endl; DO(y1 = constLValue); DO(y2 = nonConstLValue); DO(y3 = MakeConstY()); DO(y4 = MakeY()); DO(y4.validate() ); cout << "---------------------------------------" << endl; } #if 1 { cout << "+++++++++++++++++++++++++++++++++++++++" << endl; DO(Z nonConstZLValue(A_PTR)); DO(const Z constZLValue(A_PTR)); DO(Z z1(constZLValue)); DO(Z z2(nonConstZLValue)); DO(Z z3(MakeConstZ())); DO(Z z4(MakeZ())); DO(z4.validate() ); cout << "#######################################" << endl; { cout << "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" << endl; DO(z1 = constZLValue); DO(z2 = nonConstZLValue); DO(z3 = MakeConstZ()); DO(z4 = MakeZ()); DO(z4.validate() ); cout << "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" << endl; } } #endif { cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; DO(y1 = y2 + y3); DO(y1.validate() ); DO(y1 = y2 + y3 + y4); DO(y1.validate() ); DO(y1 = Y(A_PTR) + y3 + y4); DO(y1.validate() ); DO(y1 = MakeY() + MakeY() + MakeY()); DO(y1.validate() ); cout << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl; } { cout << "TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT" << endl; DO(Y y1(A_PTR)); DO( TakeY(y1) ); DO(TakeConstY(Y(A_PTR) )); DO(Z z1(A_PTR)); DO(TakeZ( z1 )); DO(TakeConstZ(Z(A_PTR) )); cout << "000000000000000000000000000000000000000" << endl; } cout << "sizeof(Y) = " << sizeof (Y) << '\n'; cout << "sizeof(Z) = " << sizeof (Z) << '\n'; cout << "sizeof(TestPtr) = " << sizeof (TestPtr) << '\n'; { DO(W w1(A_PTR)); DO(W w2(MakeW())); // ok construction from temporary. DO(w1 = MakeW()); // ok assignment from fnresult temporary. //DO(W w3(w1)); // fails to link. //DO(w1 = w2); // fails to link. } { cout << "GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG" << endl; typedef ::std::vector<Y> Y_Vec_t; DO(Y_Vec_t y_vec); DO(y_vec.push_back(MakeY() )); DO(y_vec[0].validate()); cout << "HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH" << endl; } { cout << "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII" << endl; DO( V v1(A_PTR)); DO( v1 = MakeV()); DO( V v2(MakeV())); cout << "RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR" << endl; } { cout << "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" << endl; DO( U u1(A_PTR)); DO( u1 = MakeU()); DO( U u2(MakeU())); cout << "RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR" << endl; } DO(cout << sizeof(Y) << " "); DO(cout << sizeof(Z) << " "); DO(cout << sizeof(W) << " "); DO(cout << sizeof(X) << " "); DO(cout << sizeof(TestPtr) << "\n"); }