My intent is the following: receive a rvalue reference (i.e. a reference to an object I want to cannibalize), remove some of its resources, and then return the other resources. I wrote this code:
std::vector<int> dosomething(std::vector<int> && vec) {
// do something with vec (e.g. consume SOME of its resources)
return std::move(vec);
}
but I'm not sure if:
std::forward
? I believe not since this is no universal referenceis this the correct way to achieve what I want?
OK, let's ignore the fact that vector<int>
is not a type where it's reasonable to abscond with only some of its resources (it only has one resource: the allocation). I'll pretend you're talking about some type which actually has multiple resources.
I would consider such a function to be misrepresenting itself.
A function which declares that it takes a type as an rvalue reference is saying something very important about what it will do with that value. It is saying that it is going to either directly or indirectly perform movement of that object into another object of that type. It may do it itself, or it may call someone else who will. But that is what is going to happen before the function returns.
The idea is to make this code mean exactly what it says:
SomeFunction(std::move(value));
It looks like value
is being moved into SomeFunction
, just as surely as if you did SomeType t = std::move(value);
. In both cases, the state of value
should be moved-from. To do otherwise makes the code confusing.
A function which wishes to arbitrarily modify a parameter, leaving it in some new state, should take the parameter as a non-const lvalue reference, not an rvalue reference. Exactly what "modify" means is up to the function and what it needs to do. If you want to abscond with some resources and not others, you can do that with a non-const lvalue reference. You can even std::move
from a subobject of a non-const lvalue reference. But you and your users should understand what the state of the object will be after the function call.
When you take a parameter by rvalue reference, that's shorthand for "when I return, this object will be in the moved-from state." Please don't misrepresent rvalue references as just non-const lvalue references.
My intent is the following: receive a rvalue reference (i.e. a reference to an object I want to cannibalize), remove some of its resources, and then return the other resources.
That's ok, but it's probably better to either:
remove the resource placeholders completely, or
replace the removed resources with other objects, or
indicate to the caller which resources were removed
Because if you don't do one of these, then the user may use one of the moved-from items that you cannibalised. Since moved-from objects (at least in the STL) have 'valid but undefined' state, the use of these cannibalised objects will have undefined results.
You don't want that.
is this the correct way to achieve what I want?
It seems fine
Should I use std::forward ? I believe not since this is no universal reference
No, you shouldn't. Your reasoning is correct.
FYI: 'indicating to the users which resources were removed' has a model in the standard library. Have a look at std::remove_if
- it actually moves the 'removed' items to the end of the sequence and returns an iterator with marks the end
of the remaining items and the beginning of the 'removed' ones.
Here's one way I might approach the problem.
objective: do something with all strings lexically greater than "ccc" in a vector, leaving the rest in the vector in original order
template<class T>
void do_something_with(T t)
{
std::cout << "did something with " << t << std::endl;
}
template<class ForwardIt, class UnaryPredicate>
ForwardIt rotate_if(ForwardIt first, ForwardIt last, UnaryPredicate p)
{
while(first != last)
{
auto n = std::next(first);
if (p(*first))
{
if (n != last) {
std::rotate(first, n, last);
first = n;
last = std::prev(last);
}
else {
last = first;
break;
}
}
first = n;
}
return last;
}
template<class T, class Pred, class Model>
std::vector<T> do_something_if(std::vector<T>&& in, Pred pred, Model&& model)
{
auto result = std::move(in);
auto ibreak = rotate_if(std::begin(result),
std::end(result),
[&](auto& v) {
auto match = pred(v, model);
return match;
});
for (auto i = ibreak ; i != std::end(result) ; ++i) {
do_something_with(std::move(*i));
}
result.erase(ibreak, std::end(result));
return result;
}
void test()
{
std::vector<std::string> words = {
"yyy",
"aaa",
"zzz",
"bbb",
};
auto filtered = do_something_if(std::move(words), std::greater<>(), "ccc");
std::cout << "remaining items:\n";
for (auto& w : filtered) {
std::cout << w << std::endl;
}
}
expected output:
did something with zzz
did something with yyy
remaining items:
aaa
bbb
There will be more efficient solutions in most circumstances but this one avoids memory allocations.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With