0

For example, if I have a function:

struct my_type {
   void add(some_type st) {
       values_.emplace_back(
           std::move(st)
       );
   }
   vector<some_type> values_;
};

And I call the member:

int main() {
    my_type mt;
    some_type st;
    mt.add(std::move(st));
}

I've moved st into the argument for add, which them just turns right around and moves it into a container it's managing.

This turns up quite a lot where I've got wrapper around some underlying container.

Does the C++ language allow/require some optimization of this "double move" pattern? Or should I be writing separate lvalue and rvalue add functions?

gct
  • 14,100
  • 15
  • 68
  • 107
  • 1
    Yes, an implementation can and will elliminate the move if the function is inlined. But I would probably make `add` a variadic template with universal references and perfect forwarding, because I can, or just in case `some_type` grows some constructors while I'm on vacation. – n. m. could be an AI Sep 09 '19 at 03:27
  • How would having explicit move constructors affect it? – gct Sep 09 '19 at 03:52
  • @n.m I would not unless this is generic code. 99/100 types will be fine with by-value methods here, and if the type is fixed we know if it is the 1/100. – Yakk - Adam Nevraumont Sep 09 '19 at 04:13
  • You could have the add function accept by perfect forwarding – M.M Sep 09 '19 at 04:27
  • An inlined function that does nothing but perfectly forwards its arguments to another function should not have any runtime costs, regardless of what exactly it forwards. – n. m. could be an AI Sep 09 '19 at 04:55
  • @Yakk-AdamNevraumont do you know how many constructors some_type has? How many will it acquire during program development?Is it copyable? Movable? Will it always remain such? Do you want to care? – n. m. could be an AI Sep 09 '19 at 04:57
  • Possibly relevant question: [Why are there two overloads for vector::push_back?](https://stackoverflow.com/q/28130531/580083). – Daniel Langr Sep 09 '19 at 05:40
  • `void add(some_type&& st)` is an alternative, and disallow implicit copy and so force `mt.add(some_type(st))`. – Jarod42 Sep 09 '19 at 09:05

1 Answers1

1

A move is a cast to an rvalue reference.

What that does depends on what function or ctor you pass it to.

In your case, it depend on what some_type does when you construct an instance from a some_type&&.

That is literaly arbitrary code that you haven't included.

If some_type is written with a simple and efficient move ctor, and it doesn't leak its identity, under as-if the intermediate instance could be eliminated. On the other hand, if some_type leaks identity or does other similar and/or bad things, the intermediate object may have to practically exist.

As an example, a std::vector as some_type could probably be eliminated; and if not, you wouldn't care, as a move is 3 pointers copied and zeroed. A type that prints its address when copied to std err, probably not.

There is currently nothing similar to elision that allows lifetime merging despite side effects. There have been some proposals along that direction, but I am unaware of any getting far (which mostly is evidence of my ignorance, not evidence of lack of existence), and I doubt they cover this case.

If you don't know what elision is, you should go learn; you need new vocabulary before you can discuss this issue practically. Talking about this issue in C++ without understanding C++ elision is like discussing e^(ipi)+1=0 with someone who doesn't know what multiplication is. Possible, but long winded.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I know what a move is, and I know what type elision is, I was asking specifically about anything in C++ that would allow/require the double move to be eliminated, as when copying the compiler is allowed to elide copies even when there are are side effects in the copy constructor. – gct Sep 09 '19 at 13:07
  • @gct So, see punultimate paragraph – Yakk - Adam Nevraumont Sep 09 '19 at 13:08