2

According to this Wiki page the following code:

#include <iostream>

struct C {
  C() = default;
  C(const C&) { std::cout << "A copy was made.\n"; }
};

C f() {
  return C();
}

int main() {
  std::cout << "Hello World!\n";
  C obj = f();
}

may produce different outputs depending on the compiler and that compiler's settings.

How can I write programs if I cannot predict how they're going to behave (I cannot really know what any given compiler in any given version with any given settings will or will not optimize away)? Are there any general guidelines regarding copy-elision that I should follow?

NPS
  • 6,003
  • 11
  • 53
  • 90
  • 4
    Respect rule of 3/5/0, and don't depend of copy-elision presence/absence. – Jarod42 Mar 29 '21 at 10:48
  • 3
    Notice that your example is predictable in C++17 with no copies. – Jarod42 Mar 29 '21 at 10:52
  • In addition to @Jarod42 comment: [How does guaranteed copy elision work?](https://stackoverflow.com/questions/38043319/how-does-guaranteed-copy-elision-work) – user1810087 Mar 29 '21 at 10:56
  • 1
    Can you explain what problems do you expect to happen in your program if copy elision is done or not done? – Sam Varshavchik Mar 29 '21 at 11:08
  • 1
    Constructors ought not have any *necessary* side effects (such as `std::cout` to show that particular constructor was called), because those side effects are not guaranteed to happen. Constructors are special, and the compiler is allowed to optimize them rather aggressively. – Eljay Mar 29 '21 at 11:09
  • 2
    A useful guideline is to write your code so it doesn't matter whether a copy is elided. (When it does, it's very often a sign of a design problem.) – molbdnilo Mar 29 '21 at 11:14

1 Answers1

3

Write code that doesn't depend of presence or absence of copy-elision.

It is mainly to respect rule of 3/5/0.

Here, the main purpose of your class C is to exhibit copy-elision.

In regular case, class contains data and or manage resources. So then should implement correct copy/move constructors to maintains there invariant, avoid double delete, ...

And so removing a copy is just an optimization.

There are other constructs which might depend of compiler/build/run:

  • Order of evaluation of function argument is unspecified. You should write code which doesn't depend of the order of that evaluation.

    template <typename ... Ts> void any_order_call(Ts&&...){}
    
    std::ostream& hello() { return std::cout << "hello "; }
    std::ostream& world() { return std::cout << "world"; }
    
    void maybe_hello_world()
    {
        any_order_call(world(), hello());
    }
    

    So output might be

    hello world
    

    or

    worldhello 
    
  • sizeof(int) depends of compiler/architecture, you should not assume its size. For this one, you might have test for that (if (sizeof (int) == 4)) or (optional :( ) fixed-size types (std::int32_t, ...).

  • ...

Jarod42
  • 203,559
  • 14
  • 181
  • 302