Boost logo

Boost :

Subject: [boost] [move] new rvalue reference emulation
From: Jeffrey Lee Hellrung, Jr. (jeffrey.hellrung_at_[hidden])
Date: 2011-04-04 13:36:29


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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk