Reputation: 5402
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?
Upvotes: 8
Views: 2423
Reputation: 21576
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
Upvotes: 12
Reputation: 275340
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).
Upvotes: 0
Reputation: 171117
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);
Upvotes: 1
Reputation: 1
std::move is defined to move the content, so the source container would effectively be cleared.
(or perhaps I am mis-undestanding your question)
Upvotes: -2