Thread overview
Rvalue forwarding
Feb 23, 2012
Martin Nowak
Feb 23, 2012
H. S. Teoh
Feb 24, 2012
Martin Nowak
February 23, 2012
Is it possible to forward rvalues through variadic templates?
Having to "move" the value for every layer is suboptimal.
What am I doing wrong?

----------

import std.algorithm : move;

void foo(Unique!Handle uniq)
{
    auto val = uniq.extract;
    assert(val._fd == 1);
    val.close();
}

void foo(Unique!Handle uniq, string)
{
    auto val = uniq.extract;
    assert(val._fd == 1);
    val.close();
}

version (none)
{
    void bar(Args...)(Args args)
    {
        foo(move(args)); // cannot forward variadic arguments ???
    }
}
else
{
    void bar(A0)(A0 a0)
    {
        foo(move(a0));
    }

    void bar(A0, A1)(A0 a0, A1 a1)
    {
        foo(move(a0), move(a1));
    }
}

void main()
{
    Unique!Handle uniq;

    uniq = Unique!Handle(Handle(1));
    assert(uniq._obj._fd == 1);
    bar(move(uniq));
    assert(uniq._obj._fd == 0);
    uniq = Unique!Handle(Handle(1));
    assert(uniq._obj._fd == 1);
    bar(move(uniq), "other arg");
    assert(uniq._obj._fd == 0);
}

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

struct Handle
{
    this(int fd)
    {
        _fd = fd;
        assert(_fd);
    }

    void close()
    {
        _fd = 0;
    }

    ~this()
    {
        assert(!_fd);
    }

    int _fd;
}

struct Unique(T)
{
    this()(auto ref T val) if(!__traits(isRef, val))
    {
        move(val, _obj);
    }

    this()(auto ref Unique!T val) if(!__traits(isRef, val))
    {
        move(val._obj, _obj);
    }

    @disable this(this);

    void opAssign(Unique val)
    {
        move(val._obj, _obj);
    }

    @property T extract()
    {
        return move(_obj);
    }

private:
    T _obj;
}
February 23, 2012
On Thu, Feb 23, 2012 at 08:15:52AM +0100, Martin Nowak wrote:
> Is it possible to forward rvalues through variadic templates? Having to "move" the value for every layer is suboptimal. What am I doing wrong?
> 
> ----------
> 
> import std.algorithm : move;
> 
> void foo(Unique!Handle uniq)
> {
>     auto val = uniq.extract;
>     assert(val._fd == 1);
>     val.close();
> }
> 
> void foo(Unique!Handle uniq, string)
> {
>     auto val = uniq.extract;
>     assert(val._fd == 1);
>     val.close();
> }
> 
> version (none)
> {
>     void bar(Args...)(Args args)
>     {
>         foo(move(args)); // cannot forward variadic arguments ???
>     }
[...]

I'm not sure, but doesn't this only work if foo() is also variadic? Otherwise I'm not sure how the compiler is supposed to determine, at compile-time, which overload of foo to call from here. And what if args contains 3 arguments, or arguments that don't match any overload of foo?

I suppose this *could* be handled by generating runtime code to decide which foo to call, but from what I understand, D doesn't support this currently.


T

-- 
Only boring people get bored. -- JM
February 24, 2012
On Thu, 23 Feb 2012 08:15:52 +0100, Martin Nowak <dawg@dawgfoto.de> wrote:

> Is it possible to forward rvalues through variadic templates?
> Having to "move" the value for every layer is suboptimal.
> What am I doing wrong?
>
A working solution was to let move return a proxy which defers the move
until it gets implicitly converted into an rvalue.

auto move(T)(ref T src)
{
    /* Non-instantiable non-copyable proxy to forward moves.
     */
    static struct Proxy(T)
    {
        static import std.algorithm;

        ~this()
        {
            if (_ptr !is null)
            {
                typeid(T).destroy(_ptr);
                _ptr = null;
            }
        }

        /* Triggers move on implicit conversion.
         */
        @property T get()
        {
            auto p = _ptr; _ptr = null;
            return std.algorithm.move(*p);
        }

        alias get this;

    private:
        @disable this();
        @disable this(this);

        this(T *p) { _ptr = p; }
        T* _ptr;
    }

    return Proxy!T(&src);
}

The proxy can be forwarded, but has the downside that one can escape references.

auto foo()
{
    int val;
    return move(val); // ouch
}

void bar()
{
    {
        int a;
        auto m = move(a);
    }
    int b = m; // ouch
}