Jump to page: 1 27  
Page
Thread overview
D's equivalent to C++'s std::move?
Feb 01, 2016
Shachar Shemesh
Feb 01, 2016
Rikki Cattermole
Feb 01, 2016
Sönke Ludwig
Feb 01, 2016
Dicebot
Feb 02, 2016
maik klein
Feb 02, 2016
Ali Çehreli
Feb 02, 2016
Ali Çehreli
Feb 03, 2016
Sönke Ludwig
Feb 03, 2016
Sönke Ludwig
Feb 03, 2016
Sönke Ludwig
Feb 13, 2016
rsw0x
Feb 13, 2016
rsw0x
Feb 03, 2016
Minas Mina
Feb 03, 2016
David Nadlinger
Feb 04, 2016
Matt Elkins
Feb 04, 2016
Matt Elkins
Feb 04, 2016
rsw0x
Feb 04, 2016
Era Scarecrow
Feb 09, 2016
Matt Elkins
Feb 09, 2016
Hara Kenji
Feb 10, 2016
Matt Elkins
Feb 11, 2016
Matt Elkins
Feb 10, 2016
Era Scarecrow
Feb 04, 2016
maik klein
Feb 04, 2016
rsw0x
Feb 04, 2016
maik klein
Feb 04, 2016
rsw0x
Feb 10, 2016
w0rp
Feb 11, 2016
Atila Neves
Feb 11, 2016
Atila Neves
Feb 11, 2016
Matt Elkins
Feb 11, 2016
Matt Elkins
Feb 11, 2016
Atila Neves
February 01, 2016
Hi all,

I have a non-copyable struct with move semantics. In other words, a struct with @disable this(this), but with working overloads for the this(copy) and opAssign.

Now I have an instance of that struct. I would like to be able to voluntarily give up ownership for the sake of another instance.

In C++, I would do something like this:

unique_ptr<int> p (new int), q;

q = std::move(p);

I am unsure what is the correct way to do this under D.

Shachar
February 02, 2016
On 02/02/16 2:21 AM, Shachar Shemesh wrote:
> Hi all,
>
> I have a non-copyable struct with move semantics. In other words, a
> struct with @disable this(this), but with working overloads for the
> this(copy) and opAssign.
>
> Now I have an instance of that struct. I would like to be able to
> voluntarily give up ownership for the sake of another instance.
>
> In C++, I would do something like this:
>
> unique_ptr<int> p (new int), q;
>
> q = std::move(p);
>
> I am unsure what is the correct way to do this under D.
>
> Shachar

So just to confirm, you want to explicitly copy a struct but not "implicitly" copy it?

struct Foo {
	@disable
	this(this);
	
	int x;
	
	Foo dup() {
		return Foo(x);	
	}
}

void main() {
	Foo a, b;
	a = Foo(7);
	b = a.dup;
	
	// will error
	// b = a;
}

Please note for this example code I have implemented dup, you wouldn't normally need to do that for structs.

Also D.learn is correct place to ask this. No the general N.G.
February 01, 2016
Am 01.02.2016 um 14:21 schrieb Shachar Shemesh:
> Hi all,
>
> I have a non-copyable struct with move semantics. In other words, a
> struct with @disable this(this), but with working overloads for the
> this(copy) and opAssign.
>
> Now I have an instance of that struct. I would like to be able to
> voluntarily give up ownership for the sake of another instance.
>
> In C++, I would do something like this:
>
> unique_ptr<int> p (new int), q;
>
> q = std::move(p);
>
> I am unsure what is the correct way to do this under D.
>
> Shachar

Should work with move() from std.algorithm:
http://dlang.org/library/std/algorithm/mutation/move.html
February 01, 2016
On Monday, 1 February 2016 at 13:21:02 UTC, Shachar Shemesh wrote:
> Hi all,
>
> I have a non-copyable struct with move semantics. In other words, a struct with @disable this(this), but with working overloads for the this(copy) and opAssign.
>
> Now I have an instance of that struct. I would like to be able to voluntarily give up ownership for the sake of another instance.


auto move (T) (ref T origin)
    if (is(T == struct))
{
    scope(exit) origin = T.init;
    return T(origin.tupleof);
}

// example:

struct S
{
    int* x;
    @disable this(this);
}

void foo (S s) { }

void main()
{
    auto s = S(new int);
    // won't compile, postblit is disabled:
    foo(s);
    // but this works, because rvalues are always moved:
    foo(move(s));
    assert(s.x is null);
}
February 01, 2016
On Monday, 1 February 2016 at 13:21:02 UTC, Shachar Shemesh wrote:
> q = std::move(p);
>
> I am unsure what is the correct way to do this under D.

Note that C++ std::move(...) doesn't do anything related to state, it is only a type cast, so it is zero-overhead.

What I have done to get semantics close to C++ is to define "moving" type and a "pointer" type that use it.

(I dislike how D deals with this.)

February 02, 2016
On Monday, 1 February 2016 at 13:52:49 UTC, Ola Fosheim Grøstad wrote:
> On Monday, 1 February 2016 at 13:21:02 UTC, Shachar Shemesh wrote:
>> q = std::move(p);
>>
>> I am unsure what is the correct way to do this under D.
>
> Note that C++ std::move(...) doesn't do anything related to state, it is only a type cast, so it is zero-overhead.
>
> What I have done to get semantics close to C++ is to define "moving" type and a "pointer" type that use it.
>
> (I dislike how D deals with this.)

Could you share this? I would be very interested in how you have approached it.


February 02, 2016
On 02/01/2016 05:21 AM, Shachar Shemesh wrote:

> I have a non-copyable struct with move semantics. In other words, a
> struct with @disable this(this), but with working overloads for the
> this(copy) and opAssign.
>
> Now I have an instance of that struct. I would like to be able to
> voluntarily give up ownership for the sake of another instance.
>
> In C++, I would do something like this:
>
> unique_ptr<int> p (new int), q;
>
> q = std::move(p);
>
> I am unsure what is the correct way to do this under D.

This question has been brought up a lot lately. I've decided to look at this more seriously yesterday.

My first observation is that if @post-blit is disabled isn't the struct already a unique type? If so, we don't need unique_ptr for variables of that type, right? Am I completely off there?

vector<unique_ptr<T>> is a valid use case but it's not possible with D's arrays because D's arrays work with .init object, which require assigning into.

So, I've played with an array type that can store non-copyable types. The main functions are emplace_back() and move_back(). One requirement is that the stored type must be idempotent regarding destruction of its .init value.

Have you used something similar before? Is this a correct approach to this problem?

(Pardon the non-D naming convention that uses underscores.)

/* An array that can store non-copyable types. */
struct UniqueArray(T) {
    ubyte[] storage;
    size_t count;

    @disable this(this);

    this(size_t size) {
        storage = new ubyte[](size);
    }

    ~this() {
        foreach (i; 0 .. count) {
            destroy_at(i);
        }
    }

    /* Returns the address of element at index 'i'.*/
    T* addressOf(size_t i) {
        const offset = T.sizeof * i;
        return cast(T*)(storage.ptr + offset);
    }

    /* Returns a range to all elements. TODO: Use operator overloading. */
    auto all() {
        auto arr = (cast(T*)(storage.ptr))[0..count];
        T*[] result;
        foreach (ref i; arr) {
            result ~= &i;
        }
        return result;
    }

    import std.typecons : Flag, Yes, No;

    /* Emplaces a new object at index 'i' with the given constructor
     * arguments. */
    T* emplace_at(Flag!"occupied" occupied = Yes.occupied,
                  Args...)(size_t i, Args args) {
        import std.exception : enforce;
        import std.conv : emplace;

        enforce(i <= count);

        T* place = addressOf(i);

        /* TODO: Be exception-safe; don't destroy before succesful
         * construction. */
        if (occupied) {
            destroy_at(i);
        }

        emplace(place, args);
        return place;
    }

    /* Emplaces an object at the end with the given constructor arguments. */
    T* emplace_back(Args...)(Args args) {
        if (storage.length == (count * T.sizeof)) {
            storage.length += T.sizeof;
        }

        const isOccupied = false;
        T* place = emplace_at!(No.occupied)(count, args);
        ++count;
        return place;
    }

    /* Moves an lvalue to the end. The arguments becomes T.init. */
    void move_back(ref T s) {
        import std.algorithm : move;

        T* place = emplace_back();
        move(s, *place);
    }

    /* Destroys the element at index 'i'. */
    void destroy_at(size_t i) {
        destroy(*addressOf(i));
    }
}

UniqueArray!S uniqueArray(S)(size_t size = 0) {
    return UniqueArray!S(size);
}

/* Test code follows. */

import std.stdio;

/* A non-copyable type. */
struct S {
    int i = -1;

    @disable this(this);

    void printInfo(string func = __FUNCTION__)() {
        writefln("%s for %s", func, i);
    }

    this(int i) {
        this.i = i;
        printInfo();
    }

    ~this() {
        printInfo();

        if (i == -1) {
            /* This type does not do anything special for its .init value. */
            writefln("  ... (Skipping cleanup for .init)");

        } else {
            writefln("  Doing proper cleanup");
        }
    }
}

void main() {
    auto u = uniqueArray!S();

    /* Some are emplaced back, some are moved back. */
    foreach (i; 0 .. 5) {
        if (i % 2) {
            writefln("Emplacing rvalue %s back", i);
            u.emplace_back(i);

        } else {
            writefln("Making lvalue %s", i);
            auto s = S(i);
            writefln("Moving lvalue %s back", i);
            u.move_back(s);

            assert(s == S.init);
        }
    }

    writefln("Replacing 2 with 100");
    u.emplace_at(2, 100);

    writefln("Destroying %s", 1);
    u.destroy_at(1);

    /* Just do someting with element states. NOTE: writeln(u) does not work
     * because 'u' is not copyable. */
    foreach (i, ref e; u.all) {
        writefln("%s: %s", i, e.i);
    }

    writefln("Leaving main");
}

For convenience, here is the output of the program:

Making lvalue 0
deneme.S.this for 0
Moving lvalue 0 back
deneme.S.~this for -1
  ... (Skipping cleanup for .init)
deneme.S.~this for -1
  ... (Skipping cleanup for .init)
Emplacing rvalue 1 back
deneme.S.this for 1
Making lvalue 2
deneme.S.this for 2
Moving lvalue 2 back
deneme.S.~this for -1
  ... (Skipping cleanup for .init)
deneme.S.~this for -1
  ... (Skipping cleanup for .init)
Emplacing rvalue 3 back
deneme.S.this for 3
Making lvalue 4
deneme.S.this for 4
Moving lvalue 4 back
deneme.S.~this for -1
  ... (Skipping cleanup for .init)
deneme.S.~this for -1
  ... (Skipping cleanup for .init)
Replacing 2 with 100
deneme.S.~this for 2
  Doing proper cleanup
deneme.S.this for 100
Destroying 1
deneme.S.~this for 1
  Doing proper cleanup
0: 0
1: -1
2: 100
3: 3
4: 4
Leaving main
deneme.S.~this for 0
  Doing proper cleanup
deneme.S.~this for -1
  ... (Skipping cleanup for .init)
deneme.S.~this for 100
  Doing proper cleanup
deneme.S.~this for 3
  Doing proper cleanup
deneme.S.~this for 4
  Doing proper cleanup

Ali

February 02, 2016
On Tuesday, 2 February 2016 at 21:18:27 UTC, maik klein wrote:
> Could you share this? I would be very interested in how you have approached it.

My ad hoc pointer experiments try to be general and is convoluted, so not the best example. The basic idea is to have a pointer type that match "&&" in C++ (which cannot be done completely).

So we have something like:

struct moving(T){
   T* ptr;
  ~this(){ assert(ptr is null); } // optional check to ensure that the move is completed
}

then have a move() function that returns a moving!T struct

In you pointer struct you match opAssign(moving!T ...) to emulate "operator=(T&& ...)" and set the ptr field to null to prevent it from being used again (if you want that extra check).

February 02, 2016
On Tuesday, 2 February 2016 at 22:36:22 UTC, Ali Çehreli wrote:
> This question has been brought up a lot lately. I've decided to look at this more seriously yesterday.

Nice to see that others are playing around with this, I don't have time to check your code, but one key issue with move semantics is exception safety.

AFAICT D's "std.move" is insufficient, as it would null out the original pointer prematurely and when an exception is thrown the resource will disappear rather than simple remain "unmoved".

February 02, 2016
On 02/02/2016 03:09 PM, Ola Fosheim Grøstad wrote:
> On Tuesday, 2 February 2016 at 22:36:22 UTC, Ali Çehreli wrote:
>> This question has been brought up a lot lately. I've decided to look
>> at this more seriously yesterday.
>
> Nice to see that others are playing around with this, I don't have time
> to check your code, but one key issue with move semantics is exception
> safety.
>
> AFAICT D's "std.move" is insufficient, as it would null out the original
> pointer prematurely and when an exception is thrown the resource will
> disappear rather than simple remain "unmoved".
>

Exactly. I've saved my rear end by inserting a TODO comment just before posting the code: :p

        /* TODO: Be exception-safe; don't destroy before succesful
         * construction. */
        if (occupied) {
            destroy_at(i);
        }

        emplace(place, args);

Ali

« First   ‹ Prev
1 2 3 4 5 6 7