Exact moment of “return” in a C++-function

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
17
down vote

favorite
3












It seems like a silly question, but is the exact moment at which return xxx; is "executed" in a function unambiguously defined?



Please see the following example to see what I mean (here live):



#include <iostream>
#include <string>
#include <utility>

//changes the value of the underlying buffer
//when destructed
class Writer
public:
std::string &s;
Writer(std::string &s_):s(s_)
~Writer()
s+="B";

;

std::string make_string_ok()
std::string res("A");
Writer w(res);
return res;



int main()
std::cout<<make_string_ok()<<std::endl;



What I expect to happen, while make_string_ok is called:



  1. Constructor for res is called (value of res is "A")

  2. Constructor for w is called


  3. return res is executed. The current value of res should be returned (by copying the current value of res), i.e. "A".

  4. Destructor for w is called, the value of res becomes "AB".

  5. Destructor for res is called.

So I would expect "A"as result, but get "AB" printed on the console.



On the other hand, for a slightly different version of make_string:



std::string make_string_fail()
std::pair<std::string, int> res"A",0;
Writer w(res.first);
return res.first;



the result is as expected - "A" (see live).



Does the standard prescribes which value should be returned in the examples above or is it unspecified?










share|improve this question























  • Handy reading: What are copy elision and return value optimization?
    – user4581301
    2 hours ago














up vote
17
down vote

favorite
3












It seems like a silly question, but is the exact moment at which return xxx; is "executed" in a function unambiguously defined?



Please see the following example to see what I mean (here live):



#include <iostream>
#include <string>
#include <utility>

//changes the value of the underlying buffer
//when destructed
class Writer
public:
std::string &s;
Writer(std::string &s_):s(s_)
~Writer()
s+="B";

;

std::string make_string_ok()
std::string res("A");
Writer w(res);
return res;



int main()
std::cout<<make_string_ok()<<std::endl;



What I expect to happen, while make_string_ok is called:



  1. Constructor for res is called (value of res is "A")

  2. Constructor for w is called


  3. return res is executed. The current value of res should be returned (by copying the current value of res), i.e. "A".

  4. Destructor for w is called, the value of res becomes "AB".

  5. Destructor for res is called.

So I would expect "A"as result, but get "AB" printed on the console.



On the other hand, for a slightly different version of make_string:



std::string make_string_fail()
std::pair<std::string, int> res"A",0;
Writer w(res.first);
return res.first;



the result is as expected - "A" (see live).



Does the standard prescribes which value should be returned in the examples above or is it unspecified?










share|improve this question























  • Handy reading: What are copy elision and return value optimization?
    – user4581301
    2 hours ago












up vote
17
down vote

favorite
3









up vote
17
down vote

favorite
3






3





It seems like a silly question, but is the exact moment at which return xxx; is "executed" in a function unambiguously defined?



Please see the following example to see what I mean (here live):



#include <iostream>
#include <string>
#include <utility>

//changes the value of the underlying buffer
//when destructed
class Writer
public:
std::string &s;
Writer(std::string &s_):s(s_)
~Writer()
s+="B";

;

std::string make_string_ok()
std::string res("A");
Writer w(res);
return res;



int main()
std::cout<<make_string_ok()<<std::endl;



What I expect to happen, while make_string_ok is called:



  1. Constructor for res is called (value of res is "A")

  2. Constructor for w is called


  3. return res is executed. The current value of res should be returned (by copying the current value of res), i.e. "A".

  4. Destructor for w is called, the value of res becomes "AB".

  5. Destructor for res is called.

So I would expect "A"as result, but get "AB" printed on the console.



On the other hand, for a slightly different version of make_string:



std::string make_string_fail()
std::pair<std::string, int> res"A",0;
Writer w(res.first);
return res.first;



the result is as expected - "A" (see live).



Does the standard prescribes which value should be returned in the examples above or is it unspecified?










share|improve this question















It seems like a silly question, but is the exact moment at which return xxx; is "executed" in a function unambiguously defined?



Please see the following example to see what I mean (here live):



#include <iostream>
#include <string>
#include <utility>

//changes the value of the underlying buffer
//when destructed
class Writer
public:
std::string &s;
Writer(std::string &s_):s(s_)
~Writer()
s+="B";

;

std::string make_string_ok()
std::string res("A");
Writer w(res);
return res;



int main()
std::cout<<make_string_ok()<<std::endl;



What I expect to happen, while make_string_ok is called:



  1. Constructor for res is called (value of res is "A")

  2. Constructor for w is called


  3. return res is executed. The current value of res should be returned (by copying the current value of res), i.e. "A".

  4. Destructor for w is called, the value of res becomes "AB".

  5. Destructor for res is called.

So I would expect "A"as result, but get "AB" printed on the console.



On the other hand, for a slightly different version of make_string:



std::string make_string_fail()
std::pair<std::string, int> res"A",0;
Writer w(res.first);
return res.first;



the result is as expected - "A" (see live).



Does the standard prescribes which value should be returned in the examples above or is it unspecified?







c++ language-lawyer c++17






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 2 hours ago

























asked 2 hours ago









ead

10.5k21851




10.5k21851











  • Handy reading: What are copy elision and return value optimization?
    – user4581301
    2 hours ago
















  • Handy reading: What are copy elision and return value optimization?
    – user4581301
    2 hours ago















Handy reading: What are copy elision and return value optimization?
– user4581301
2 hours ago




Handy reading: What are copy elision and return value optimization?
– user4581301
2 hours ago












3 Answers
3






active

oldest

votes

















up vote
10
down vote













Due to Return Value Optimization (RVO), a destructor for std::string res in make_string_ok may not be called. The string object can be constructed on the caller's side and the function may only initialize the value.



The code will be equivalent to:



void make_string_ok(std::string& res)
Writer w(res);


int main()
std::string res("A");
make_string_ok(res);



That is why the value return shall be "AB".



In the second example, RVO does not apply, and the value will be copied to the returned value exactly upon the call to return, and Writer's destructor will run on res.first after the copy occurred.




6.6 Jump statements



On exit from a scope (however accomplished), destructors (12.4) are
called for all constructed objects with automatic storage duration
(3.7.2) (named objects or temporaries) that are declared in that
scope, in the reverse order of their declaration. Transfer out of a
loop, out of a block, or back past an initialized variable with
automatic storage duration involves the destruction of variables with
automatic storage duration that are in scope at the point transferred
from...



...



6.6.3 The Return Statement



The copy-initialization of the returned entity is sequenced before the
destruction of temporaries at the end of the full-expression
established by the operand of the return statement, which, in turn, is
sequenced before the destruction of local variables (6.6) of the block
enclosing the return statement.







share|improve this answer






















  • I also thought about that, but what surprises me - the results are different, so it is not only about cutting out a copying. Would like to know, what the standard says about it.
    – ead
    2 hours ago










  • added a quote of the standard
    – Shloim
    2 hours ago










  • “destructor” in your first sentence is supposed to be “copy constructor”, isn’t it? Otherwise I think the answer makes no sense.
    – Konrad Rudolph
    20 mins ago

















up vote
6
down vote













It's RVO, one of the optimization that are allowed to change visible behaviour:



10.9.5 Copy/move elision (emphases are mine):




When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object.



This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):




  • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function parameter or a variable introduced by the exception-declaration of a handler) with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function call's return object

  • [...]



So, your reasoning is wrong at points 3. and 4. It's up to compiler for those steps to be executed.



Meanwhile, your second example does not fit the criterion (nor the ones omitted for brevity) because the types are different. So the writer dies. It would even die, if it were a part of the pair.






share|improve this answer



























    up vote
    4
    down vote













    There is a concept in C++ called elision.



    Elision takes two seemingly distinct objects and merges their identity and lifetime.



    Prior to c++17 elision could occur:



    1. When you have a non-parameter variable Foo f; in a function that returned Foo and the return statement was a simple return f;.


    2. When you have an anonymous object being used to construct pretty much any other object.


    In c++17 all (almost?) cases of #2 are eliminated by the new prvalue rules; elision no longer occurs, because what used to create a temporary object no longer does so. Instead, the construction of the "temporary" is directly bound to the permanent object location.



    Now, elision isn't always possible given the ABI that a compiler compiles to. Two common cases where it is possible are known as Return Value Optimization and Named Return Value Optimization.



    RVO is the case like this:



    Foo func() 
    return Foo(7);

    Foo foo = func();


    where we have a return value Foo(7) which is elided into the value returned, which is then elided into the external variable foo. What appears to be 3 objects (the return value of foo(), the value on the return line, and Foo foo) is actually 1 at runtime.



    Prior to c++17 the copy/move constructors must exist here, and the elision is optional; in c++17 due to the new prvalue rules no copy/move constructr need exist, and there is no option for the compiler, there must be 1 value here.



    The other famouse case is named return value optimization, NRVO. This is the (1) elision case above.



    Foo func() 
    Foo local;
    return local;

    Foo foo = func();


    again, elision can merge the lifetime and identity of of Foo local, the return value from func and Foo foo outside of func.



    Even c++17, the second merge (between func's return value and Foo foo) is non-optional (and technically the prvalue returned from func is never an object, just an expression, which is then bound to construct Foo foo), but the first remains optional, and requires a move or copy constructor to exist.



    Elision is a rule that can occur even if eliminating those copies, destructions and constructions would have observable side effects; it is not an "as-if" optimization. Instead, it is subtle change away from what a naive person might think C++ code means. Calling it an "optimization" is more than a bit of a misnomer.



    The fact it is optional, and that subtle things can break it, is an issue with it.



    Foo func(bool b) 
    Foo long_lived;
    long_lived.futz();
    if (b)

    Foo short_lived;
    return short_lived;

    return long_lived;



    in the above case, while it is legal for a compiler to elide both Foo long_lived and Foo short_lived, implementation issues make it basically impossible, as both objects cannot both have their lifetimes merged with the return value of func; eliding short_lived and long_lived together is not legal, and their lifetimes overlap.



    You can still do it under as-if, but only if you can examine and understand all side effects of destructors, constructors and .futz().






    share|improve this answer






















      Your Answer





      StackExchange.ifUsing("editor", function ()
      StackExchange.using("externalEditor", function ()
      StackExchange.using("snippets", function ()
      StackExchange.snippets.init();
      );
      );
      , "code-snippets");

      StackExchange.ready(function()
      var channelOptions =
      tags: "".split(" "),
      id: "1"
      ;
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function()
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled)
      StackExchange.using("snippets", function()
      createEditor();
      );

      else
      createEditor();

      );

      function createEditor()
      StackExchange.prepareEditor(
      heartbeatType: 'answer',
      convertImagesToLinks: true,
      noModals: false,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: 10,
      bindNavPrevention: true,
      postfix: "",
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      );



      );













       

      draft saved


      draft discarded


















      StackExchange.ready(
      function ()
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52931095%2fexact-moment-of-return-in-a-c-function%23new-answer', 'question_page');

      );

      Post as a guest






























      3 Answers
      3






      active

      oldest

      votes








      3 Answers
      3






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes








      up vote
      10
      down vote













      Due to Return Value Optimization (RVO), a destructor for std::string res in make_string_ok may not be called. The string object can be constructed on the caller's side and the function may only initialize the value.



      The code will be equivalent to:



      void make_string_ok(std::string& res)
      Writer w(res);


      int main()
      std::string res("A");
      make_string_ok(res);



      That is why the value return shall be "AB".



      In the second example, RVO does not apply, and the value will be copied to the returned value exactly upon the call to return, and Writer's destructor will run on res.first after the copy occurred.




      6.6 Jump statements



      On exit from a scope (however accomplished), destructors (12.4) are
      called for all constructed objects with automatic storage duration
      (3.7.2) (named objects or temporaries) that are declared in that
      scope, in the reverse order of their declaration. Transfer out of a
      loop, out of a block, or back past an initialized variable with
      automatic storage duration involves the destruction of variables with
      automatic storage duration that are in scope at the point transferred
      from...



      ...



      6.6.3 The Return Statement



      The copy-initialization of the returned entity is sequenced before the
      destruction of temporaries at the end of the full-expression
      established by the operand of the return statement, which, in turn, is
      sequenced before the destruction of local variables (6.6) of the block
      enclosing the return statement.







      share|improve this answer






















      • I also thought about that, but what surprises me - the results are different, so it is not only about cutting out a copying. Would like to know, what the standard says about it.
        – ead
        2 hours ago










      • added a quote of the standard
        – Shloim
        2 hours ago










      • “destructor” in your first sentence is supposed to be “copy constructor”, isn’t it? Otherwise I think the answer makes no sense.
        – Konrad Rudolph
        20 mins ago














      up vote
      10
      down vote













      Due to Return Value Optimization (RVO), a destructor for std::string res in make_string_ok may not be called. The string object can be constructed on the caller's side and the function may only initialize the value.



      The code will be equivalent to:



      void make_string_ok(std::string& res)
      Writer w(res);


      int main()
      std::string res("A");
      make_string_ok(res);



      That is why the value return shall be "AB".



      In the second example, RVO does not apply, and the value will be copied to the returned value exactly upon the call to return, and Writer's destructor will run on res.first after the copy occurred.




      6.6 Jump statements



      On exit from a scope (however accomplished), destructors (12.4) are
      called for all constructed objects with automatic storage duration
      (3.7.2) (named objects or temporaries) that are declared in that
      scope, in the reverse order of their declaration. Transfer out of a
      loop, out of a block, or back past an initialized variable with
      automatic storage duration involves the destruction of variables with
      automatic storage duration that are in scope at the point transferred
      from...



      ...



      6.6.3 The Return Statement



      The copy-initialization of the returned entity is sequenced before the
      destruction of temporaries at the end of the full-expression
      established by the operand of the return statement, which, in turn, is
      sequenced before the destruction of local variables (6.6) of the block
      enclosing the return statement.







      share|improve this answer






















      • I also thought about that, but what surprises me - the results are different, so it is not only about cutting out a copying. Would like to know, what the standard says about it.
        – ead
        2 hours ago










      • added a quote of the standard
        – Shloim
        2 hours ago










      • “destructor” in your first sentence is supposed to be “copy constructor”, isn’t it? Otherwise I think the answer makes no sense.
        – Konrad Rudolph
        20 mins ago












      up vote
      10
      down vote










      up vote
      10
      down vote









      Due to Return Value Optimization (RVO), a destructor for std::string res in make_string_ok may not be called. The string object can be constructed on the caller's side and the function may only initialize the value.



      The code will be equivalent to:



      void make_string_ok(std::string& res)
      Writer w(res);


      int main()
      std::string res("A");
      make_string_ok(res);



      That is why the value return shall be "AB".



      In the second example, RVO does not apply, and the value will be copied to the returned value exactly upon the call to return, and Writer's destructor will run on res.first after the copy occurred.




      6.6 Jump statements



      On exit from a scope (however accomplished), destructors (12.4) are
      called for all constructed objects with automatic storage duration
      (3.7.2) (named objects or temporaries) that are declared in that
      scope, in the reverse order of their declaration. Transfer out of a
      loop, out of a block, or back past an initialized variable with
      automatic storage duration involves the destruction of variables with
      automatic storage duration that are in scope at the point transferred
      from...



      ...



      6.6.3 The Return Statement



      The copy-initialization of the returned entity is sequenced before the
      destruction of temporaries at the end of the full-expression
      established by the operand of the return statement, which, in turn, is
      sequenced before the destruction of local variables (6.6) of the block
      enclosing the return statement.







      share|improve this answer














      Due to Return Value Optimization (RVO), a destructor for std::string res in make_string_ok may not be called. The string object can be constructed on the caller's side and the function may only initialize the value.



      The code will be equivalent to:



      void make_string_ok(std::string& res)
      Writer w(res);


      int main()
      std::string res("A");
      make_string_ok(res);



      That is why the value return shall be "AB".



      In the second example, RVO does not apply, and the value will be copied to the returned value exactly upon the call to return, and Writer's destructor will run on res.first after the copy occurred.




      6.6 Jump statements



      On exit from a scope (however accomplished), destructors (12.4) are
      called for all constructed objects with automatic storage duration
      (3.7.2) (named objects or temporaries) that are declared in that
      scope, in the reverse order of their declaration. Transfer out of a
      loop, out of a block, or back past an initialized variable with
      automatic storage duration involves the destruction of variables with
      automatic storage duration that are in scope at the point transferred
      from...



      ...



      6.6.3 The Return Statement



      The copy-initialization of the returned entity is sequenced before the
      destruction of temporaries at the end of the full-expression
      established by the operand of the return statement, which, in turn, is
      sequenced before the destruction of local variables (6.6) of the block
      enclosing the return statement.








      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited 2 hours ago









      Nicol Bolas

      276k33451619




      276k33451619










      answered 2 hours ago









      Shloim

      2,2321019




      2,2321019











      • I also thought about that, but what surprises me - the results are different, so it is not only about cutting out a copying. Would like to know, what the standard says about it.
        – ead
        2 hours ago










      • added a quote of the standard
        – Shloim
        2 hours ago










      • “destructor” in your first sentence is supposed to be “copy constructor”, isn’t it? Otherwise I think the answer makes no sense.
        – Konrad Rudolph
        20 mins ago
















      • I also thought about that, but what surprises me - the results are different, so it is not only about cutting out a copying. Would like to know, what the standard says about it.
        – ead
        2 hours ago










      • added a quote of the standard
        – Shloim
        2 hours ago










      • “destructor” in your first sentence is supposed to be “copy constructor”, isn’t it? Otherwise I think the answer makes no sense.
        – Konrad Rudolph
        20 mins ago















      I also thought about that, but what surprises me - the results are different, so it is not only about cutting out a copying. Would like to know, what the standard says about it.
      – ead
      2 hours ago




      I also thought about that, but what surprises me - the results are different, so it is not only about cutting out a copying. Would like to know, what the standard says about it.
      – ead
      2 hours ago












      added a quote of the standard
      – Shloim
      2 hours ago




      added a quote of the standard
      – Shloim
      2 hours ago












      “destructor” in your first sentence is supposed to be “copy constructor”, isn’t it? Otherwise I think the answer makes no sense.
      – Konrad Rudolph
      20 mins ago




      “destructor” in your first sentence is supposed to be “copy constructor”, isn’t it? Otherwise I think the answer makes no sense.
      – Konrad Rudolph
      20 mins ago












      up vote
      6
      down vote













      It's RVO, one of the optimization that are allowed to change visible behaviour:



      10.9.5 Copy/move elision (emphases are mine):




      When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object.



      This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):




      • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function parameter or a variable introduced by the exception-declaration of a handler) with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function call's return object

      • [...]



      So, your reasoning is wrong at points 3. and 4. It's up to compiler for those steps to be executed.



      Meanwhile, your second example does not fit the criterion (nor the ones omitted for brevity) because the types are different. So the writer dies. It would even die, if it were a part of the pair.






      share|improve this answer
























        up vote
        6
        down vote













        It's RVO, one of the optimization that are allowed to change visible behaviour:



        10.9.5 Copy/move elision (emphases are mine):




        When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object.



        This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):




        • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function parameter or a variable introduced by the exception-declaration of a handler) with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function call's return object

        • [...]



        So, your reasoning is wrong at points 3. and 4. It's up to compiler for those steps to be executed.



        Meanwhile, your second example does not fit the criterion (nor the ones omitted for brevity) because the types are different. So the writer dies. It would even die, if it were a part of the pair.






        share|improve this answer






















          up vote
          6
          down vote










          up vote
          6
          down vote









          It's RVO, one of the optimization that are allowed to change visible behaviour:



          10.9.5 Copy/move elision (emphases are mine):




          When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object.



          This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):




          • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function parameter or a variable introduced by the exception-declaration of a handler) with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function call's return object

          • [...]



          So, your reasoning is wrong at points 3. and 4. It's up to compiler for those steps to be executed.



          Meanwhile, your second example does not fit the criterion (nor the ones omitted for brevity) because the types are different. So the writer dies. It would even die, if it were a part of the pair.






          share|improve this answer












          It's RVO, one of the optimization that are allowed to change visible behaviour:



          10.9.5 Copy/move elision (emphases are mine):




          When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object.



          This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):




          • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function parameter or a variable introduced by the exception-declaration of a handler) with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function call's return object

          • [...]



          So, your reasoning is wrong at points 3. and 4. It's up to compiler for those steps to be executed.



          Meanwhile, your second example does not fit the criterion (nor the ones omitted for brevity) because the types are different. So the writer dies. It would even die, if it were a part of the pair.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered 2 hours ago









          luk32

          12.1k2245




          12.1k2245




















              up vote
              4
              down vote













              There is a concept in C++ called elision.



              Elision takes two seemingly distinct objects and merges their identity and lifetime.



              Prior to c++17 elision could occur:



              1. When you have a non-parameter variable Foo f; in a function that returned Foo and the return statement was a simple return f;.


              2. When you have an anonymous object being used to construct pretty much any other object.


              In c++17 all (almost?) cases of #2 are eliminated by the new prvalue rules; elision no longer occurs, because what used to create a temporary object no longer does so. Instead, the construction of the "temporary" is directly bound to the permanent object location.



              Now, elision isn't always possible given the ABI that a compiler compiles to. Two common cases where it is possible are known as Return Value Optimization and Named Return Value Optimization.



              RVO is the case like this:



              Foo func() 
              return Foo(7);

              Foo foo = func();


              where we have a return value Foo(7) which is elided into the value returned, which is then elided into the external variable foo. What appears to be 3 objects (the return value of foo(), the value on the return line, and Foo foo) is actually 1 at runtime.



              Prior to c++17 the copy/move constructors must exist here, and the elision is optional; in c++17 due to the new prvalue rules no copy/move constructr need exist, and there is no option for the compiler, there must be 1 value here.



              The other famouse case is named return value optimization, NRVO. This is the (1) elision case above.



              Foo func() 
              Foo local;
              return local;

              Foo foo = func();


              again, elision can merge the lifetime and identity of of Foo local, the return value from func and Foo foo outside of func.



              Even c++17, the second merge (between func's return value and Foo foo) is non-optional (and technically the prvalue returned from func is never an object, just an expression, which is then bound to construct Foo foo), but the first remains optional, and requires a move or copy constructor to exist.



              Elision is a rule that can occur even if eliminating those copies, destructions and constructions would have observable side effects; it is not an "as-if" optimization. Instead, it is subtle change away from what a naive person might think C++ code means. Calling it an "optimization" is more than a bit of a misnomer.



              The fact it is optional, and that subtle things can break it, is an issue with it.



              Foo func(bool b) 
              Foo long_lived;
              long_lived.futz();
              if (b)

              Foo short_lived;
              return short_lived;

              return long_lived;



              in the above case, while it is legal for a compiler to elide both Foo long_lived and Foo short_lived, implementation issues make it basically impossible, as both objects cannot both have their lifetimes merged with the return value of func; eliding short_lived and long_lived together is not legal, and their lifetimes overlap.



              You can still do it under as-if, but only if you can examine and understand all side effects of destructors, constructors and .futz().






              share|improve this answer


























                up vote
                4
                down vote













                There is a concept in C++ called elision.



                Elision takes two seemingly distinct objects and merges their identity and lifetime.



                Prior to c++17 elision could occur:



                1. When you have a non-parameter variable Foo f; in a function that returned Foo and the return statement was a simple return f;.


                2. When you have an anonymous object being used to construct pretty much any other object.


                In c++17 all (almost?) cases of #2 are eliminated by the new prvalue rules; elision no longer occurs, because what used to create a temporary object no longer does so. Instead, the construction of the "temporary" is directly bound to the permanent object location.



                Now, elision isn't always possible given the ABI that a compiler compiles to. Two common cases where it is possible are known as Return Value Optimization and Named Return Value Optimization.



                RVO is the case like this:



                Foo func() 
                return Foo(7);

                Foo foo = func();


                where we have a return value Foo(7) which is elided into the value returned, which is then elided into the external variable foo. What appears to be 3 objects (the return value of foo(), the value on the return line, and Foo foo) is actually 1 at runtime.



                Prior to c++17 the copy/move constructors must exist here, and the elision is optional; in c++17 due to the new prvalue rules no copy/move constructr need exist, and there is no option for the compiler, there must be 1 value here.



                The other famouse case is named return value optimization, NRVO. This is the (1) elision case above.



                Foo func() 
                Foo local;
                return local;

                Foo foo = func();


                again, elision can merge the lifetime and identity of of Foo local, the return value from func and Foo foo outside of func.



                Even c++17, the second merge (between func's return value and Foo foo) is non-optional (and technically the prvalue returned from func is never an object, just an expression, which is then bound to construct Foo foo), but the first remains optional, and requires a move or copy constructor to exist.



                Elision is a rule that can occur even if eliminating those copies, destructions and constructions would have observable side effects; it is not an "as-if" optimization. Instead, it is subtle change away from what a naive person might think C++ code means. Calling it an "optimization" is more than a bit of a misnomer.



                The fact it is optional, and that subtle things can break it, is an issue with it.



                Foo func(bool b) 
                Foo long_lived;
                long_lived.futz();
                if (b)

                Foo short_lived;
                return short_lived;

                return long_lived;



                in the above case, while it is legal for a compiler to elide both Foo long_lived and Foo short_lived, implementation issues make it basically impossible, as both objects cannot both have their lifetimes merged with the return value of func; eliding short_lived and long_lived together is not legal, and their lifetimes overlap.



                You can still do it under as-if, but only if you can examine and understand all side effects of destructors, constructors and .futz().






                share|improve this answer
























                  up vote
                  4
                  down vote










                  up vote
                  4
                  down vote









                  There is a concept in C++ called elision.



                  Elision takes two seemingly distinct objects and merges their identity and lifetime.



                  Prior to c++17 elision could occur:



                  1. When you have a non-parameter variable Foo f; in a function that returned Foo and the return statement was a simple return f;.


                  2. When you have an anonymous object being used to construct pretty much any other object.


                  In c++17 all (almost?) cases of #2 are eliminated by the new prvalue rules; elision no longer occurs, because what used to create a temporary object no longer does so. Instead, the construction of the "temporary" is directly bound to the permanent object location.



                  Now, elision isn't always possible given the ABI that a compiler compiles to. Two common cases where it is possible are known as Return Value Optimization and Named Return Value Optimization.



                  RVO is the case like this:



                  Foo func() 
                  return Foo(7);

                  Foo foo = func();


                  where we have a return value Foo(7) which is elided into the value returned, which is then elided into the external variable foo. What appears to be 3 objects (the return value of foo(), the value on the return line, and Foo foo) is actually 1 at runtime.



                  Prior to c++17 the copy/move constructors must exist here, and the elision is optional; in c++17 due to the new prvalue rules no copy/move constructr need exist, and there is no option for the compiler, there must be 1 value here.



                  The other famouse case is named return value optimization, NRVO. This is the (1) elision case above.



                  Foo func() 
                  Foo local;
                  return local;

                  Foo foo = func();


                  again, elision can merge the lifetime and identity of of Foo local, the return value from func and Foo foo outside of func.



                  Even c++17, the second merge (between func's return value and Foo foo) is non-optional (and technically the prvalue returned from func is never an object, just an expression, which is then bound to construct Foo foo), but the first remains optional, and requires a move or copy constructor to exist.



                  Elision is a rule that can occur even if eliminating those copies, destructions and constructions would have observable side effects; it is not an "as-if" optimization. Instead, it is subtle change away from what a naive person might think C++ code means. Calling it an "optimization" is more than a bit of a misnomer.



                  The fact it is optional, and that subtle things can break it, is an issue with it.



                  Foo func(bool b) 
                  Foo long_lived;
                  long_lived.futz();
                  if (b)

                  Foo short_lived;
                  return short_lived;

                  return long_lived;



                  in the above case, while it is legal for a compiler to elide both Foo long_lived and Foo short_lived, implementation issues make it basically impossible, as both objects cannot both have their lifetimes merged with the return value of func; eliding short_lived and long_lived together is not legal, and their lifetimes overlap.



                  You can still do it under as-if, but only if you can examine and understand all side effects of destructors, constructors and .futz().






                  share|improve this answer














                  There is a concept in C++ called elision.



                  Elision takes two seemingly distinct objects and merges their identity and lifetime.



                  Prior to c++17 elision could occur:



                  1. When you have a non-parameter variable Foo f; in a function that returned Foo and the return statement was a simple return f;.


                  2. When you have an anonymous object being used to construct pretty much any other object.


                  In c++17 all (almost?) cases of #2 are eliminated by the new prvalue rules; elision no longer occurs, because what used to create a temporary object no longer does so. Instead, the construction of the "temporary" is directly bound to the permanent object location.



                  Now, elision isn't always possible given the ABI that a compiler compiles to. Two common cases where it is possible are known as Return Value Optimization and Named Return Value Optimization.



                  RVO is the case like this:



                  Foo func() 
                  return Foo(7);

                  Foo foo = func();


                  where we have a return value Foo(7) which is elided into the value returned, which is then elided into the external variable foo. What appears to be 3 objects (the return value of foo(), the value on the return line, and Foo foo) is actually 1 at runtime.



                  Prior to c++17 the copy/move constructors must exist here, and the elision is optional; in c++17 due to the new prvalue rules no copy/move constructr need exist, and there is no option for the compiler, there must be 1 value here.



                  The other famouse case is named return value optimization, NRVO. This is the (1) elision case above.



                  Foo func() 
                  Foo local;
                  return local;

                  Foo foo = func();


                  again, elision can merge the lifetime and identity of of Foo local, the return value from func and Foo foo outside of func.



                  Even c++17, the second merge (between func's return value and Foo foo) is non-optional (and technically the prvalue returned from func is never an object, just an expression, which is then bound to construct Foo foo), but the first remains optional, and requires a move or copy constructor to exist.



                  Elision is a rule that can occur even if eliminating those copies, destructions and constructions would have observable side effects; it is not an "as-if" optimization. Instead, it is subtle change away from what a naive person might think C++ code means. Calling it an "optimization" is more than a bit of a misnomer.



                  The fact it is optional, and that subtle things can break it, is an issue with it.



                  Foo func(bool b) 
                  Foo long_lived;
                  long_lived.futz();
                  if (b)

                  Foo short_lived;
                  return short_lived;

                  return long_lived;



                  in the above case, while it is legal for a compiler to elide both Foo long_lived and Foo short_lived, implementation issues make it basically impossible, as both objects cannot both have their lifetimes merged with the return value of func; eliding short_lived and long_lived together is not legal, and their lifetimes overlap.



                  You can still do it under as-if, but only if you can examine and understand all side effects of destructors, constructors and .futz().







                  share|improve this answer














                  share|improve this answer



                  share|improve this answer








                  edited 1 hour ago

























                  answered 2 hours ago









                  Yakk - Adam Nevraumont

                  174k19178359




                  174k19178359



























                       

                      draft saved


                      draft discarded















































                       


                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function ()
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52931095%2fexact-moment-of-return-in-a-c-function%23new-answer', 'question_page');

                      );

                      Post as a guest













































































                      Comments

                      Popular posts from this blog

                      White Anglo-Saxon Protestant

                      BuddyTV

                      Conflict (narrative)