Will any compiler actually ever elide these copies?
Asked Answered
D

2

6

Given

struct Range{
    Range(double from, double to) : from(from), to(to) {}
    double from;
    double to;
    // if it matters to the compiler, we can add more fields here to make copying expensive
};

struct Box{
    Box(Range x, Range y) : x(x), y(y) {}
    Range x;
    Range y;
};

someone said that in Box box(Range(0.0,1.0),Range(0.0,2.0)), the compiler can avoid copying Range objects altogether by constructing them inside box to begin with.

Does any compiler actually do this?

My own attempts haven't succeeded.

Diann answered 23/11, 2015 at 14:8 Comment(15)
If your compiler is a descent one, rest assure. It will elide them.Unlisted
@101010 Are any particular modern compilers decent? I can't make it happen.Diann
You should be commenting on that answer without making a duplicate question.Maelstrom
@Maelstrom That question was interpreted as "can it, in principle" and I didn't want to change it to "does it, actually" after answers had been written.Diann
The answer on there which says it doesn't elide constructors actually does elide two copies.Socinus
It should really be a supplement question, saying "I'm using this compiler... it didn't optimize it". You still haven't mentioned your compiler and flags...Maelstrom
@Socinus Indeed, but the question (both here and there) is about totally avoiding copying.Diann
@tennenrishin the compiler won't elide copies from a parameter to a data member, but you can std::move for efficiency.Socinus
@Socinus So the answer I linked to is actually incorrect?Diann
@tennenrishin well it's correct in that temporaries copied into objects of the same type can have the copy elided. The problem is that the template parameter is not a temporary.Socinus
@tennenrishin Since you ask about eliding all the copies, I would say the answer is wrong. But I think only one set of copies can be elided anyway. So one could argue the expectation is misplaced.Medrek
@MikeMB Hang on, both questions are explicitly about copy elision.Medrek
@tennenrishin I didn't say that!!!Unlisted
@tennenrishin Also this mislead caused unnecessary down-votes on a valid answer. Don't do that again please. Try to comment on the answers if you want any clarification. Don't post another question. You cause a mislead.Unlisted
@101010 When I asked this as a separate question it was because I wanted to accept your answer to my original question rather than changing the question. It is, after all, possible that compilers can (i.e. are allowed to) do something that none of them actually do.Diann
R
8

The compiler can - and normally does - elide the copies from the temporary to the argument. The compiler cannot elide the copy from the argument to members. While it may technically possible to elide these copies in some cases, the relevant permission isn't given. The section of the standard is 12.8 [class.copy] paragraph 31 which spells out 4 situations where a copy can be elided (the exact rules are a bit non-trivial):

  1. When returning a named, function local variable using it name.
  2. When using a named, function local variable in a throw expression.
  3. When copying a temporary object.
  4. When catching an exception by value.

Passing a named argument as parameter to the construction of a member variable is, clearly, none of these situations.

The essential background of the rules for copy elision is that in some contexts the declaration of functions suffice to determine when an object will be used. If it is clear upon construction time where the object can be constructed, it can be elided. The caller of a constructor cannot determine based only on the declaration of the constructor where the object will be used.

Rue answered 23/11, 2015 at 14:50 Comment(7)
As a complement to the above (very good) answer, I would like to add that one way of staying in control is to use (maybe const) reference or pointer arguments, and only copy when it is really necessary. That way, you know exactly what happens and no unnecessary copies are made, and you are basically independent of what the compiler writer has done in terms of optimizations. Also, you do not have to try to decipher the difficult-to-read standards documents.Barcelona
@ErikAlapää using const references interferes with move semantics; I'd say it is better to pass by value here . In this specific code, we can replace x(x) by x(std::move(x)) etc. resulting in (for each parameter) 2 moves of which 1 can be elided; but if changing to const references, there is one copy which cannot be elided.Clemen
@Clemen Interesting - I am starting to understand rvalue refs and std::move, but I still tend to use old-style proven methods ;) But still, if I use a pointer, I can make sure I minimize the copying, since I explicitly have to copy to get a copy.Barcelona
@ErikAlapää: The key issue here is that you may get away using only a move and never using a copy. For the case of the Ranges it doesn't matter: a copy and move is just equivalent. Assume, however, instead of a Range the argument is more like a std::vector<T>: when the argument is passed by value it is created by a move or a copy, either of which are possibly elided. Since it is known that the value won't be used otherwise it can safely be moved from. The trade here is to possibly just move and in the worst case to copy and move against a guaranteed copy.Sapient
@DietmarKühl: OK, good clarification. I have discussed std::move and rvalue refs many times, but I still tend to use pointers, since I understand and control 100% what happens. All I read from the standard concerning copy elision in various cases are options, not guarantees that copying will not be done. To use it in e.g. high-performance and/or real-time systems, I want guarantees from the standard and from the compiler.Barcelona
@ErikAlapää rule of thumb, if the function would need to make a copy of an argument passed by const reference, it may be better to pass by valueClemen
@ErikAlapää: I tried to create a more thorough explanation at a better to locate place: https://mcmap.net/q/541120/-why-should-arguments-be-passed-by-value-when-used-to-initialize-another-objectSapient
U
0

That Someone is me. So let me clear my stand.

I never said that in Box box(Range(0.0,1.0),Range(0.0,2.0)), the compiler can avoid copying Range objects altogether by constructing them inside box to begin with. What I said was:

Yes it can, In particular this kind of copy elision context falls under the copy elision criterion specified in 12.8/p31.3 Copying and moving class objects [class.copy] of the standard:

(31.3) -- when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same type (ignoring cv-qualification), the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move.

The Yes it can, part goes for the temporary objects passed in the constructor (That can be elide per standard as mentioned above). I never said that the parameters can be elided all the way to the Box constructor's initializer list.

After all, that case doesn't qualify for any of the criteria where copy elision can be applied as per standard.

I also said that even if a certain context is qualified as a context where copy elision can be applied, the compiler is not obligated to follow. If you rely on that effects then your program is not considered portable.

Unlisted answered 23/11, 2015 at 19:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.