Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannibalizing just some of the resources of an rvalue ref

Tags:

c++

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:

  1. is this the correct way to achieve what I want?
  2. Should I use std::forward ? I believe not since this is no universal reference
like image 619
Dean Avatar asked May 12 '16 17:05

Dean


2 Answers

is 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.

like image 69
Nicol Bolas Avatar answered Nov 16 '22 02:11

Nicol Bolas


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.

like image 26
Richard Hodges Avatar answered Nov 16 '22 01:11

Richard Hodges