7

Is it possible to write a function move_and_clear such that, for any STL container:

do_something_with(move_and_clear(container));

is equivalent to:

do_something_with(std::move(container));
container.clear();

?

Here is my first attempt, which doesn't work. I think I got the types right (although a production version of this would probably sprinkle in some std::remove_reference's), and it compiles successfuly, but it fails or crashes because scratch is accessed after it goes out of scope.

template<class T>
T &&move_and_clear(T &t)
{
    T scratch;
    std::swap(scratch, t);
    return std::move(scratch);
}

Here is my second attempt. This actually works, but it's a preprocessor macro, and is therefore evil:

template<class T>
T &&move_and_clear_helper(T &t, T &&scratch)
{
    std::swap(scratch, t);
    return std::move(scratch);
}
#define move_and_clear(t) move_and_clear_helper(t, decltype(t)())

My third attempt is another macro which also works, this time using a lambda instead of a named helper function. So it's a bit more self-contained than the previous macro, but perhaps less readable, and of course it's still evil because it's a macro:

#define move_and_clear(t) \
    [](decltype(t) &tparam, decltype(t) &&scratch){ \
        std::swap(scratch, tparam); \
        return std::move(scratch); \
    }(t, decltype(t)())

Here is a compilable program incorporating my three attempts:

/*
    g++ --std=c++11 -W -Wall -g move_and_clear.cc -o move_and_clear1 -DWHICH=1
    g++ --std=c++11 -W -Wall -g move_and_clear.cc -o move_and_clear2 -DWHICH=2
    g++ --std=c++11 -W -Wall -g move_and_clear.cc -o move_and_clear3 -DWHICH=3
    ./move_and_clear1   # assert-fails
    ./move_and_clear2   # succeeds
    ./move_and_clear3   # succeeds
*/

#include <assert.h>
#include <iostream>
#include <memory>
#include <vector>

#if WHICH == 1
    template<class T>
    T &&move_and_clear(T &t)
    {
        T scratch;
        std::swap(scratch, t);
        return std::move(scratch);
    }
#elif WHICH == 2
    template<class T>
    T &&move_and_clear_helper(T &t, T &&scratch)
    {
        std::swap(scratch, t);
        return std::move(scratch);
    }
    #define move_and_clear(t) move_and_clear_helper(t, decltype(t)())
#elif WHICH == 3
    #define move_and_clear(t) \
        [](decltype(t) &tparam, decltype(t) &&scratch){ \
            std::swap(scratch, tparam); \
            return std::move(scratch); \
        }(t, decltype(t)())
#endif

// Example "do_something_with":
// takes an rvalue reference to a vector that must have size 3,
// steals its contents, and leaves it in a valid but unspecified state.
// (Implementation detail: leaves it with 7 elements.)
template<typename T>
void plunder3_and_leave_in_unspecified_state(std::vector<T> &&v)
{
  assert(v.size() == 3);
  std::vector<T> pirate(7);
  assert(pirate.size() == 7);
  std::swap(pirate, v);
  assert(pirate.size() == 3);
  assert(v.size() == 7);
}

int main(int, char**)
{
    {
        std::cout << "Using std::move and clear ..." << std::endl << std::flush;
        std::vector<std::unique_ptr<int>> v(3);
        assert(v.size() == 3);
        plunder3_and_leave_in_unspecified_state(std::move(v));
        assert(v.size() == 7); // (uses knowledge of plunder's impl detail)
        v.clear();
        assert(v.empty());
        std::cout << "done." << std::endl << std::flush;
    }
    {
        std::cout << "Using move_and_clear ..." << std::endl << std::flush;
        std::vector<std::unique_ptr<int>> v(3);
        assert(v.size() == 3);
        plunder3_and_leave_in_unspecified_state(move_and_clear(v));
        assert(v.empty());
        std::cout << "done." << std::endl << std::flush;
    }
}

Is there a way to implement move_and_clear as a template function, without using a macro?

Don Hatch
  • 5,041
  • 3
  • 31
  • 48
  • "clear" in what sense? -Unless, you also want to clear the underlying memory allocation used by the container, why not simply use the `clear()` member function? - BTW, `std::move` already steals the contents – WhiZTiM Jun 24 '16 at 11:21
  • @WhiZTiM "clear" in the sense of the container's clear() member function, as I said in my explicit description of the question at the beginning. Because, for example, I have code that calls std::move followed by clear(), in a hundred different places, and it's too easy to forget or misplace the clear(), so I want to combine them into a single call if possible. – Don Hatch Jun 24 '16 at 11:25
  • Isn't it better to call `clear()` from `do_something_with()`? – el.pescado - нет войне Jun 24 '16 at 11:32
  • @el.pescado Perhaps, but I didn't write do_something_with() so that isn't an option. For example, say do_something_with() is a move constructor, which leaves the source in a "valid but unspecified state". – Don Hatch Jun 24 '16 at 11:34
  • @DonHatch Of the standard containers, only `std::string` would plausibly not clear (when SBO occurs) when moved-from, and even there I suspect every implementation will do the tiny bit of work to set the size to 0. In practice, actually moving (not calling `std::move`, but doing the move and consuming the result) will clear. Are you just being paranoid, or do you have a practical example where `std::move` doesn't do what you are asking to happen? – Yakk - Adam Nevraumont Jun 24 '16 at 13:16
  • @Yakk I suppose "paranoid" is one way of putting it. Where I work, we take the standard at face value when it says "valid but unspecified state"; so failing to clear the container before reusing it after a move is simply considered an error and won't pass code review, nor will it pass a sufficiently smart compiler. I honestly don't know how common clearing or not-clearing happens to be in current move implementations, and it doesn't seem like an interesting question to me-- very similar to the question of what's in memory after it's been free()d, we simply don't make assumptions about it. – Don Hatch Jun 24 '16 at 13:45
  • @JustasKuksta I see there is a suggested edit of the title from you, but I don't understand it. It looks like it changes the title to "is it possible to implement a std:ect:x/move-and-clear function?" What is this doing? Is that going to come out better, in some way that I can't see until I accept it? – Don Hatch Nov 14 '20 at 16:32

4 Answers4

11

Is it possible to write a function move_and_clear such that, for any STL container:

do_something_with(move_and_clear(container));

is equivalent to:

do_something_with(std::move(container));
container.clear();

?

template<typename T>
T move_and_clear(T& data){
     T rtn(std::move(data));
     data.clear();
     return rtn;
}

The return value will be treated as an rvalue at the call site.

Again, that will enjoy the benefits of Return Value Optimization (in any sane compiler). And most definitely, inlining. See Howard Hinnant's answer to this question.


Again, STL containers have move constructors, but for any other custom container, it's better to constrain it to move-constructible types. Else, you may call it with a conatiner that doesn't move, and you have an necessary copy there.

template<typename T>
auto move_and_clear(T& data)
-> std::enable_if_t<std::is_move_constructible<T>::value, T>
{
     T rtn(std::move(data));
     data.clear();
     return rtn;
}

See: This answer

EDIT:

If you have fears about RVO, I don't know any major compiler that wouldn't do an RVO there in optimized builds (except explicitly turned off by a switch). There is also a proposal to make it mandatory, hopefully we should see that in C++17.

EDIT2:

The paper made it into the Working Draft of C++17, See this

Community
  • 1
  • 1
WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
  • 1
    Shouldn't argument be `T&& data`? – el.pescado - нет войне Jun 24 '16 at 11:34
  • No, that's a bad idea: Using a *Forwarding Reference* `T&&` will allow that function to even bind to a temporary and a `const` object, which I am not sure it makes much sense since you cannot move from a `const` – WhiZTiM Jun 24 '16 at 11:37
  • And what about return value? This function returns `T` by value, doesn't that defeat `std::move`? – el.pescado - нет войне Jun 24 '16 at 11:42
  • Ok your code seems to work in my test program, which surprises me since I thought the function would have to return T&& (like std::move does). I guess you're taking advantage of RVO but I don't know whether that will work in all cases, and like @el.pescado says it seems to be doing a different task from what std::move does. I need to look into this more. I wonder if perhaps I didn't write a hard enough test program.. – Don Hatch Jun 24 '16 at 11:47
  • 2
    @el.pescado, No it doesn't, Return Value Optimization will surely kick in *(in any sane compiler)*. Using `std::move` in your return statement can actually be a performance pessimization or worse cause a bug. See [Howard Hinnant's answer to this](http://stackoverflow.com/questions/4986673/c11-rvalues-and-move-semantics-confusion-return-statement) question. – WhiZTiM Jun 24 '16 at 11:49
  • 1
    @DonHatch, See [Howard Hinnant's answer to this question](http://stackoverflow.com/questions/4986673/c11-rvalues-and-move-semantics-confusion-return-statement). The above is the simplest and efficient way to do this. Although, the standard doesn't mandate RVO to kick in. I don't know *any major* compiler that wouldn't do an RVO there in optimized builds (except explicitly turned off by a switch). There is a [proposal to make it mandatory](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0135r0.html), hopefully we should see that in C++17. – WhiZTiM Jun 24 '16 at 12:00
  • Ok I think I have a point of view taking form. First of all RVO is an *optimization* which means it can't be depended on, so we need to write something that will work whether or not RVO happens. However, the functioning of your code is not dependent on whether RVO happens or not. The crucial point is that this return-by-value must be done without any copy-constructing (since there is no copy-constructor for a vector)... which might be a safe bet even if no RVO. So, are we *guaranteed* that the return-by-value you're doing here can be done without copy construction in all cases? – Don Hatch Jun 24 '16 at 12:10
  • 2
    @DonHatch Yes, that's pretty much guaranteed. When doing `return x;` for a local `x`, the standard **forces** the compiler to treat `x` as an rvalue first (that is, move from it). Only if that fails is copying considered. – Angew is no longer proud of SO Jun 24 '16 at 12:16
  • @Angew if that's the case, then it looks like this is a great answer. However I'm still very perturbed by the fact that this function returns a different type from what std::move returns, when it's supposed to be a drop-in replacement (with added value). Can I conclude that it would actually be possible to re-spec std::move itself in such a way that it returns a T rather than a T&&? If not, what would that break? And the thing it would break, is that a thing for which WhiZTiM's function won't work either? – Don Hatch Jun 24 '16 at 12:27
  • @DonHatch Yes, you could re-implement `std::move(x)` from `return static_cast(x)` to `return T(static_cast(x))`. The net effect would be an extra move happening inside (where there is none currently), but its use would remain the same. – Angew is no longer proud of SO Jun 24 '16 at 12:35
  • @Angew ... and presumably that extra move gets optimized away by RVO in the normal case, exactly the same as in WhiZTiM's function. All right, I'm convinced... accepting! – Don Hatch Jun 24 '16 at 12:46
  • @DonHatch Strange: I swear I read a version without `.clear()`. Possibly I read it during a 5 minute edit grace-window. Comment deleted as obsolete. – Yakk - Adam Nevraumont Jun 24 '16 at 20:09
  • The constraint is pointless and certainly *won't* have the "avoid unnecessary copy" effect. – T.C. Jun 25 '16 at 18:27
  • @T.C. Thanks boss. I just remembered [this](http://stackoverflow.com/a/7055102/1621391) ... I will remove that part – WhiZTiM Jun 25 '16 at 18:43
1

Here's an implementation which doesn't require an extra move of the container, but introduces a proxy object:

template <class T>
class ClearAfterMove
{
  T &&object;

public:
  ClearAfterMove(T &&object) : object(std::move(object)) {}

  ClearAfterMove(ClearAfterMove&&) = delete;

  ~ClearAfterMove() { object.clear(); }

  operator T&& () const { return std::move(object); }
};


template <class T>
ClearAfterMove<T> move_and_clear(T &t)
{
  return { std::move(t) };
}

How this works is that it creates a non-movable object ClearAfterMove, which will wrap the source container t, and call clear on t when it (the wrapper) goes out of scope.

In a call like this:

do_something_with(move_and_clear(container));

A temporary ClearAfterMove object (let's call it cam) wrapping container will be created by the call to move_and_clear. This temporary will then be converted to an rvalue reference by its conversion operator and passed on to do_something_with.

Temporaries go out of scope at the end of the full-expression in which they were created. For cam, this means it will be destroyed once the call to do_something_with is resolved, which is exactly what you want.

Note that this has an advantage of not producing any extra moves, and a disadvantage which all proxy object solutions have: it doesn't play nice with type deduction, such as:

auto x = move_and_clear(y);
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • +1 Nice! This is more in line with my original attempts than @WhiZTiM's answer is, and this doesn't bend my brain as much as his answer does. However his is shorter, and I assume they are both equivalent after optimizations... so I'll try to get used to his way. – Don Hatch Jun 24 '16 at 12:58
0
template<class T>
T &&move_and_clear(T &t)
{
  T scratch;
  std::swap(scratch, t);
  return std::move(scratch);
}

this is close. You typed to much however:

template<class T>
T move_and_clear(T &t)
{
  T scratch;
  std::swap(scratch, t);
  return scratch;
}

scratch will be elided with the return value (barring someone setting hostile compiler flags).

A slight improvement:

template<class T>
T move_and_clear(T &t)
{
  T scratch;
  using std::swap;
  swap(scratch, t);
  return scratch;
}

the enables Koenig lookup on swap, which means if someone wrote a free swap operation on their container that is more efficient than std::swap, it would be called instead.


Now, in practice, a successful move of a std container will clear it. The only one where not clearing the source container makes even a lick of sense is std::basic_string when the SSO (small string optimization) is active, and there the effort required to do the clear is so tiny I cannot see a library writer not doing it for the sake of sanity.

However, this code with the swap guarantees that the source object will have the state of an empty object of type T.

Note that this does not work in all possible C++ implementations:

template<class T>
T move_and_clear(T &t)
{
  T scratch = std::move(t);
  return scratch;
}

For most std containers, the requirements on move-construction are more strict, and iterators must transfer. Iterators transfering means that it must clear the source in practice.

On std::basic_string, however, iterators do not have to transfer due to the small string optimization. I know of no guarantee that the source basic_string will be cleared (but I could easily be wrong: there could be a clause in the standard that explicitly requires that: it is just not implied by the other semantics of the operation).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I'm having a hard time figuring out your position; maybe a little bit of rewording for clarification would help. On one hand, you seem to advocate relying on the assumption that moves clear (except maybe for std::basic_string?), especially in your first comment to the original question. But then you seem to be saying the opposite, i.e. that it can't be relied on ("Note that this does *not* work [...]"). Maybe just add "But this can't be relied on." to the end of your first paragraph that begins with "Now in practice" (if that's what you intend to say.) – Don Hatch Jun 24 '16 at 20:14
  • You make an interesting statement "Iterators transferring means that it must clear the source in practice". Can you say more about why this implication holds? On the face of it, I don't see why.. though I haven't thought deeply about it. – Don Hatch Jun 24 '16 at 20:17
-2

std::move is defined to move the content, so the source container would effectively be cleared.

(or perhaps I am mis-undestanding your question)

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • 2
    It is not required that the moved container is empty BTW. – Jarod42 Jun 24 '16 at 11:28
  • 5
    No, std::move doesn't move anything; see http://en.cppreference.com/w/cpp/utility/move . And according to that reference, "Unless otherwise specified, all standard library objects that have been moved from are placed in a valid but unspecified state." So my aim is to write something that *does* behave as you describe, instead. – Don Hatch Jun 24 '16 at 11:29