Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

universal reference c++11 code duplication

In my project I have function like this:

bool VectorList::put(const Pair &p);

This adds the Pair into the VectorList by copying the Pair.

I can use it like this:

Pair p { "key", "value" };

VectorList v;
v.put(p);

// or
v.put(Pair{ "anotherkey", "anothervalue" });

However in second case an unnecessary object is created, so I want to do

bool VectorList::put(Pair &&p);

I checked how this is done in vector (gcc, llvm) and there is 100% same code in both methods, except equal / std::move() line.

Is there some way I could do it without code duplication?


put() looks similar to this:

struct Node{
    Pair pair;
    AdditionalThings at;
};

bool VectorList::put(const Pair &p){
    if (not_good_for_insert(p))
         return false;
    // ...
    Node node = create_node();
    node.pair = p;
    // ...
    return true;
}
like image 713
Nick Avatar asked Feb 04 '26 13:02

Nick


2 Answers

Yes, use perfect forwarding:

template <typename P>
bool VectorList::put (P &&p) {
    //can't forward p here as it could move p and we need it later
    if (not_good_for_insert(p)) 
     return false;
    // ...
    Node node = create_node();
    node.pair = std::forward<P>(p);
    // ...
    return true;
}

Another possibility is to just pass by value like in Maxim's answer. The advantage of the perfect-forwarding version is that it requires no intermediate conversions if you pass in compatible arguments and performs better if moves are expensive. The disadvantage is that forwarding reference functions are very greedy, so other overloads might not act how you want.

Note that Pair &&p is not a universal reference, it's just an rvalue reference. Universal (or forwarding) references require an rvalue in a deduced context, like template arguments.

like image 146
TartanLlama Avatar answered Feb 07 '26 02:02

TartanLlama


The ideal solution is to accept a universal reference, as TartanLlama advises.

The ideal solution works if you can afford having the function definition in the header file. If your function definition cannot be exposed in the header (e.g. you employ Pimpl idiom or interface-based design, or the function resides in a shared library), the second best option is to accept by value. This way the caller can choose how to construct the argument (copy, move, uniform initialization). The callee will have to pay the price of one move though. E.g. bool VectorList::put(Pair p);:

VectorList v;
Pair p { "key", "value" };
v.put(p); 
v.put(std::move(p));
v.put(Pair{ "anotherkey", "anothervalue" });
v.put({ "anotherkey", "anothervalue" });

And in the implementation you move from the argument:

bool VectorList::put(Pair p) { container_.push_back(std::move(p)); }

Another comment is that you may like to stick with standard C++ names for container operations, like push_back/push_front, so that it is clear what it does. put is obscure and requires readers of your code to look into the source code or documentation to understand what is going on.

like image 43
Maxim Egorushkin Avatar answered Feb 07 '26 02:02

Maxim Egorushkin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!