6

I'm reading on copy elision (and how it's supposed to be guaranteed in C++17) and this got me a bit confused (I'm not sure I know things I thought I knew before). So here's a minimal test case:

std::string nameof(int param)
{
    switch (param)
    {
    case 1: 
        return "1"; // A
    case 2: 
        return "2" // B
    }
    return std::string(); // C
}

The way I see it, cases A and B perform a direct construction on the return value so copy elision has no meaning here, while case C cannot perform copy elision because there are multiple return paths. Are these assumptions correct?

Also, I'd like to know if

  • there's a better way of writing the above (e.g. have a std::string retval; and always return that one or write cases A and B as return string("1") etc)
  • there's any move happening, for example "1" is a temporary but I'm assuming it's being used as a parameter for the constructor of std::string
  • there are optimization concerns I ommited (e.g. I believe C could be written as return{}, would that be a better choice?)
jveazey
  • 5,398
  • 1
  • 29
  • 44
Lorah Attkins
  • 5,331
  • 3
  • 29
  • 63
  • Why do you think case A is different from case C. A is effectively `std::string("1")` and C is `std::string("")` – Ed Heal Jan 18 '17 at 22:02
  • @KerrekSB Nothing – Lorah Attkins Jan 18 '17 at 22:03
  • @EdHeal I believe for case C to be equivalent to `std::string("1")` copy elision has to happen or is there no difference from directly constructing into the return value? Or are the multiple return paths messing with copy elision? Anyway, that's what I'm asking so don't ask me – Lorah Attkins Jan 18 '17 at 22:09
  • A smart compiler will construct all of these directly in the object being assigned to by the caller of the function. – Jesper Juhl Jan 18 '17 at 22:09
  • @LorahAttkins Instead of guessing what may happen, [look at the generated assembly code when optimizations are used](https://godbolt.org/g/vASovb) – PaulMcKenzie Jan 18 '17 at 22:13
  • @PaulMcKenzie I can't read assembly , could you please explain me? – Lorah Attkins Jan 18 '17 at 22:14
  • @LorahAttkins Do you see `std::string`'s being constructed in that assembly listing? Remove the `-O2` optimization and see what the unoptimized version looks like. Also, the answer to the "will the compiler do this?" type questions can always be answered by looking at what the compiler is doing, and you do that by looking at the assembly language generated (which in this context is not hard to understand). – PaulMcKenzie Jan 18 '17 at 22:15
  • 1
    I have reopened the question, as I do not believe it is the exact duplicate. – SergeyA Jan 18 '17 at 22:16
  • @SergeyA Agreed, if that were the case then every question on the description of a feature would mark as duplicates all questions on use cases of those features. I was just trying to find the related discussion on meta; furthermore there wouldn't even be a `copy elision` / `rvo` tag since there's an answer saying what they are – Lorah Attkins Jan 18 '17 at 22:21
  • @PaulMcKenzie I wanted to know what the Standard says. I wish I could read assembly and do what you suggest more often, but to my understanding, there's the implementation and then there's the canonical reference. And it's amazing to see that **tracking the Standard even in this small case is no easy task** (at least that's what it looks like based on the comments and the answers so far) – Lorah Attkins Jan 18 '17 at 22:30
  • @LorahAttkins, in this case standard is rather simple, actually. However, standard says nothing about copy elision in your case, as it doesn't talk about NRVO. See my answer on the case when standard actually has to say something. – SergeyA Jan 18 '17 at 22:33
  • @PaulMcKenzie I think in the version you linked the compiler saw the no use of `nameof` and optmized a lot of things away. Even in this [case](https://godbolt.org/g/y992uj) where I make no mention of the function I see very different codegen and actual string constructions – Lorah Attkins Jan 18 '17 at 22:54

2 Answers2

3

To make it NRVO-friendly, you should always return the same object. The value of the object might be different, but the object should be the same.

However, following above rule makes program harder to read, and often one should opt for readability over unnoticeable performance improvement. Since std::string has a move constructor defined, the difference between moving a pointer and a length and not doing so would be so tiny that I see no way of actually noticing this in the application.

As for your last question, return std::string() and return {} would be exactly the same.

There are also some incorrect statements in your question. For example, "1" is not a temporary. It's a string literal. Temporary is created from this literal.

Last, but not least, mandatory C++17 copy elision does not apply here. It is reserved for cases like

std::string x = "X";

which before the mandatory requirement could generate code to create a temporary std::string and initialize x with copy (or move) constructor.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
1

In all cases, the copy might or might not be elided. Consider:

std::string j = nameof(whatever);

This could be implemented one of two ways:

  1. Only one std::string object is ever constructed, j. (The copy is elided.)

  2. A temporary std::string object is constructed, its value is copied to j, then the temporary is destroyed. (The function returns a temporary that is copied.)

David Schwartz
  • 179,497
  • 17
  • 214
  • 278