1

I wish to create an object A that can be constructed by either copying or moving a variable number of B objects. Something like this:

    class B {}
    
    class A {
        A(B&& operand1, B&& operand2, ... B&& operandk) {
           // move construct A
        }
        
        A(B& operand1, B& operand2, ... B& operandk) {
           // copy construct B
        }
    }

I can't seem to identify a way to store r-value references in any sort of iterable container to accomplish this. I've read about std::reference_wrapper but it doesn't seem to allow for r-values. Is there a way to accomplish something like this, or is there a reason I shouldn't be trying to do this at all? Thanks very much.

jumplist
  • 13
  • 3
  • For sink parameters, you can use "either copy-or-move, whichever is optimal". So for your class A constructor example: `A(B op1, B op2, ... B opk)` – Eljay Jul 02 '20 at 17:47
  • Does this answer your question? [What are the main purposes of using std::forward and which problems it solves?](https://stackoverflow.com/questions/3582001/what-are-the-main-purposes-of-using-stdforward-and-which-problems-it-solves) – François Andrieux Jul 02 '20 at 18:11

1 Answers1

0

When you need an object to own, just take it by value. The caller can move-construct or copy-construct the arguments depending on whether they need to retain a copy of the objects they're going to give the constructor.

And -- even better -- this way they can move-construct some of the objects but not others.

In the constructor, you can freely move-construct your A subobjects using the B objects in the initializer list since you know the caller will have already copied anything that needs copying.

To accept a variable number of arguments, just have your constructor be a template accepting a parameter pack, and the arguments by value:

class A {
public:
    template <typename... T>
    A(T ...);
};

In the implementation, you can do whatever you need to after move-constructing a B from each argument. This will cause the constructor template to fail to instantiate if the arguments cannot all be used to create a B:

template <typename... T>
A::A(T ... args) {
    (... , do_something_with_a_B(B{std::move(args)}));
}
cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • ...because those elements are always `const`. – HolyBlackCat Jul 02 '20 at 17:52
  • Fixed to not use initializer lists, didn't realize the contents couldn't be moved. – cdhowie Jul 02 '20 at 18:09
  • This does an extra move per `B`. There's a tradeoff between not moving and simplicity. Since moves are cheap, we usually decide to not bother getting rid of them. But here we already have the complexity we need, so we may as well get rid of the moves. Replace `T... args` with `T&&... args` and `std::move(args)` with `std::forward(args)`. Aside: A more strict "must be `B`s" check may be `template, B> && ...), int> = 0> ...`, though I wouldn't use it (because I don't think the standard library does anything like it). – HTNW Jul 02 '20 at 18:25
  • @HTNW `std::decay` does much the same; I had an SFINAE version that would do the same using it but decided to remove that complexity for the sake of the example. – cdhowie Jul 02 '20 at 18:56
  • It seems I have a bunch of reading to do on forwarding but with my current understanding my question is, in this case wouldn't you rather just pass by lvalue reference exclusively instead of by value? Particularly in my case the pass by value would have to make a relatively expensive deep copy and at that point I might as well just copy from the lvalue reference everytime? – jumplist Jul 02 '20 at 19:10
  • @jumplist Do you mean rvalue reference? The advantage of taking a value / forwarding is the _caller_ gets to decide if a copy takes place. If the caller needs a copy, the caller can take a copy; otherwise the caller can provide an rvalue reference and the argument is move-constructed (or simply is an rvalue reference). – cdhowie Jul 02 '20 at 19:15