
I've recently discovered (well, probably rediscovered) an alternate rvalue reference emulation, one which allows rvalues ***of any arbitrary (move-emulation-enabled) type*** to be captured in C++03 and identified as rvalues, rather than references-to-const (there is a drawback, though, which I'll get to below; as usual, there's no "free lunch"). This is in contrast to the current move emulation framework Ion has created in Boost.Move which, at best, allows you to capture rvalues of specific types. That is, if you can enumerate the types for which you want to enable rvalue capture, then you're good. For example, if you want to create a generic unary function some_fn, and you know you want to at least capture rvalues of type X, you could do: template< class T > void some_fn(T&); // captures (lvalue) references-to-non-const, explicitly created emulated rvalue references, *and* (lvalue) references-to-(X const) template< class T > typename boost::disable_if< boost::is_same<T,X> >::type void some_fn(T const &); // captures (lvalue) references-to-const *except* (lvalue) references-to-(X const) void some_fn(rv<X>&); // captures rvalues of X as emulated rvalue references However, if Y is any type other than X, the above overloads will capture an rvalue of Y as an (lvalue) reference-to-(Y const). This is one of the shortcomings of using the emulated rvalue reference rv<T>& as a function argument: it can't bind to rvalues in a context where the template parameter T is deduced. Now I don't know if the following proposition is a good idea or not, but I think it is worth considering. Again, we would like the emulated rvalue reference rv<T>& to bind to rvalues, but the problem is the deduction of the T template parameter. Instead of using rv<T>& to capture arbitrary rvalues, I'll use a kind of type-erased emulated rvalue reference, generic_rv<...> (for lack of a better name at the moment), which will just store a void pointer to the original, pre-type-erased object. Of course, we need some way to recover the object back from the void pointer, and that's what the "..." template parameters are for. This is where the (unfortunate) runtime overhead comes in, as some dispatching mechanism (e.g., function pointers) needs to be used to effect the recovery of the original, pre-type-erased object. The important difference between rv<T> and generic_rv<...> is that there's no T in the template parameter list "..." of generic_rv; more to the point, its template parameters are not deduced within the context of binding function arguments of some_fn, hence the compiler will be allowed to look for conversions from arbitrary rvalues to generic_rv<...>. Hopefully that gives enough of an introduction to the idea. Further details are probably best left to a code snippet. Comments? Specifically, should this be considered as an addition to Boost.Move's current emulation framework? Has anyone seen this technique before? ---------------- #include <iostream> #include <boost/type_traits/is_convertible.hpp> #include <boost/utility/enable_if.hpp> // rv<T>& emulates T&& template< class T > class rv : public T { rv(); rv(rv const &); rv& operator=(rv const &); }; template< class T > struct has_move_emulation : boost::is_convertible< T, rv<T>& > { }; // *** NEW EMULATED RVALUE REFERENCE *** // generic_rv< F, Result > also emulates an rvalue reference, but in a kind of // type-erased way. The actual object referred to is referenced by a void // pointer. To be able to cast the void pointer back to a pointer-to-object, we // need to package the void pointer together with some kind of dispatching // mechanism. We use a function pointer to an instantiation of cast_forward<T>. template< class F, class Result = void > struct generic_rv { typedef Result (*p_cast_forward_type)(F, void*); generic_rv(p_cast_forward_type p_cast_forward, void* p) : mp_cast_forward(p_cast_forward), m_p(p) { } Result cast_forward(F f) const { return (*mp_cast_forward)(f, m_p); } template< class T > static Result cast_forward(F f, void* const p) { return f(static_cast< rv<T>& >(*static_cast< T* >(p))); } private: p_cast_forward_type mp_cast_forward; void* m_p; }; // X is just a typical move-emulation-enabled class. struct X { X() { std::cout << "X::X()" << std::endl; } X(X const &) { std::cout << "X::X(X const &)" << std::endl; } X(rv<X>&) { std::cout << "X::X(rv<X>&)" << std::endl; } X& operator=(X) { std::cout << "X::operator=(X)" << std::endl; return *this; } X& operator=(rv<X>&) { std::cout << "X::operator=(rv<X>&)" << std::endl; return *this; } operator rv<X>&() { std::cout << "X::operator rv<X>&()" << std::endl; return *static_cast< rv<X>* >(this); } operator rv<X> const &() const { std::cout << "X::operator rv<X> const &() const" << std::endl; return *static_cast< rv<X> const * >(this); } // *** NEW CONVERSION OPERATOR *** // X provides an implicit conversion to generic_rv. template< class F, class Result > operator generic_rv< F, Result >() { std::cout << "X::operator generic_rv< F, Result >()" << std::endl; return generic_rv< F, Result >( &generic_rv< F, Result >::template cast_forward<X>, static_cast< void* >(this) ); } }; struct some_fn { template< class T > void operator()(T&) const { std::cout << "some_fn::operator()(T&) const" << std::endl; } template< class T > typename boost::disable_if< has_move_emulation<T> >::type operator()(T const &) const { std::cout << "some_fn::operator()(T const &) const" << std::endl; } // Notice that this overload requires template parameter deduction, hence // the compiler cannot apply an implicit conversion to rv<T>& from rvalues // of move-emulation-enabled types. In other words, this overload can only // bind to *explicitly* created emulated rvalue references, not to "real" // rvalues :( template< class T > void operator()(rv<T>&) const { std::cout << "some_fn::operator()(rv<T>&) const" << std::endl; } // *** NEW FUNCTION OVERLOAD TO CAPTURE RVALUES *** // Since this overload requires no template parameter deduction, the // compiler *can* apply an implicit conversion to generic_rv<...> from // rvalues of move-emulation-enabled types. Yes, there will be some runtime // overhead from the function pointer dispatching, but that will often be // preferable to copying x! void operator()(generic_rv< some_fn > const x) const { std::cout << "some_fn::operator()(generic_rv< some_fn >) const" << std::endl; return x.cast_forward(*this); } }; template< class T > T make() { return T(); } int main(int argc, char* argv[]) { int a = 0; int const b = 0; some_fn()(a); // some_fn::operator()(T&) const some_fn()(b); // some_fn::operator()(T const &) const some_fn()(make< int >()); // some_fn::operator()(T const &) const X x; // X::X() X const y; // X::X() some_fn()(x); // some_fn::operator()(T&) const some_fn()(y); // some_fn::operator()(T&) const some_fn()(make<X>()); // X::X() // X::operator generic_rv< F, Result >() // some_fn::operator()(generic_rv< some_fn >) const // some_fn::operator()(rv<T>&) const return 0; } ---------------- - Jeff