View Poll Results: Have you used or seen used const references to temporaries?

Voters
5. You may not vote on this poll
  • No, Never in real code.

    3 60.00%
  • I've seen it used, but not for compelling reasons.

    1 20.00%
  • Yes, It was useful, but not mandatory

    1 20.00%
  • Yes, it's vital for a particular design

    0 0%

Thread: Const references to temporaries

  1. #1
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149

    Const references to temporaries

    A feature of C++ is that a (non-parameter) const reference variable that binds to an temporary , can extend the life of that temporary to the scope of the reference.

    Like so:
    Code:
    #include <iostream>
    struct Foo{
      Foo(){}
      ~Foo(){std::cout<<"dtor\n"<<std::flush;}
    };
    
    void do_stuff(){
      std::cout << "do stuff\n";
    }
    
    int main(){
      const Foo & example{Foo()};
      do_stuff();
      return 0;
    }
    My question is, is this feature actually useful? Have you used it intentionally? Is there any design pattern that is aided by this feature?
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  2. #2
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    O_o

    Just for the sake of information: a few C++ compilers will copy the object in any event.

    The mechanic of copying a temporary to "live" beside the reference is allowed by the C++98 standard so is conforming behavior.

    The natural code of some compilers would be something similar to:

    Code:
    #include <iostream>
    struct Foo{
      Foo(){}
      ~Foo(){std::cout<<"dtor\n"<<std::flush;}
    };
     
    void do_stuff(){
      std::cout << "do stuff\n";
    }
     
    int main(){
      Foo temp; // default object
      // Foo::constructor(&temp);
      Foo tempForReferenceLifetime(temp);
      // Foo::copy(&tempForReferenceLifetime, &temp);
      const Foo & example(tempForReferenceLifetime);
      do_stuff();
      return 0;
    }
    The "upshot" of this detail is simple: some compilers, C++98 compilers at least, will need access to a copy-constructor which may be called, all that implies, for such code.

    My question is, is this feature actually useful?
    Well, the feature proper is crucial, but as written for the example, the feature is more of a "side-effect" of crucial behavior than intent.

    Have you used it intentionally? Is there any design pattern that is aided by this feature?
    I've never seen it used for anything that doesn't, eventually, work out to be "I thought this would prevent a copy from being made.".

    Soma
    Last edited by phantomotap; 12-16-2013 at 09:15 AM.
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  3. #3
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    The case where I expected the feature to be more useful was this.
    Code:
    #include <iostream>
    struct Bar{
        Bar(){}
    };
    
    struct Foo: Bar {
        Foo(){}
      ~Foo(){std::cout<<"dtor\n"<<std::flush;}
    };
    
    void do_stuff(){
      std::cout << "do stuff\n";
    }
    
    int main(){
      const Bar & example{Foo()};
      do_stuff();
      return 0;
    }
    However, this code runs differently on clang and gcc. Which is the correct output? Are both, because of the above mentioned copying rules?
    I guess gcc's behavior makes this not a portable use.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  4. #4
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    Which is the correct output? Are both, because of the above mentioned copying rules?
    O_o

    I don't have "Clang" available on this computer, but if the difference is an extra "dtor\n" line, then yes, both are correct.

    [Edit]
    Just so as you know, the use of brace notation limits your compiler options.

    Yes, the alternative is ugly, but the result is more reliable.

    Code:
    const Bar & example((Foo()));
    [/Edit]

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  5. #5
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    Quote Originally Posted by phantomotap View Post
    O_o

    I don't have "Clang" available on this computer, but if the difference is an extra "dtor\n" line, then yes, both are correct.

    [Edit]
    Just so as you know, the use of brace notation limits your compiler options.

    Yes, the alternative is ugly, but the result is more reliable.

    Code:
    const Bar & example((Foo()));
    [/Edit]

    Soma
    gcc output:
    Code:
    dtor
    do stuff
    clang output:
    Code:
    do stuff
    dtor
    If I change to use the parenthesis, like you show, then gcc agrees with clang. So I guess it's an initializer list issue.

    Updated code:
    Code:
    #include <iostream>
    struct Bar{
    };
    
    struct Foo: Bar {
      ~Foo(){std::cout<<"dtor\n"<<std::flush;}
    };
    
    void do_stuff(){
      std::cout << "do stuff\n";
    }
    
    int main(){
      const Bar & example(  ( Foo() )  );
      do_stuff();
      return 0;
    }
    
    // output:
    do stuff
    dtor
    Last edited by King Mir; 12-16-2013 at 02:30 PM.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  6. #6
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    O_O

    Okay. Wow.

    Well, this actually has nothing to do with the allowed copy.

    You've seemingly found a legitimate bug in how "G++" implements the brace-enclosed initializer list or related paraphernalia.

    The only way that happens, barring related bugs I guess, is if "G++" is obtaining the `Bar' reference obtained from the initializer list by way of a copy-constructor. So, the `Foo' within the initilizer is extremely limited in scope, but the compiler allows the reference to live via a created object according to the initializer rules.

    In other words, yes, this is creating a copy, but the copy is created because the assumed target is converted via constructor thanks to the C++11 brace-enclosed initializer rules--partially from uniform initialization.

    Worse, this is also slicing the `Foo' object. (I'm assuming, but if the copy is being created from the target (`Bar') copy constructor as I believe "slicing" is the only possible outcome.) You may test this if you like with carefully selected virtual methods. (Specifically, reset some flag on destruction that should logically be shared by a reference to the destroyed `Foo'.)

    [Edit]
    A simpler way to test: add outputs for copying and destruction in both `Bar' and `Foo'.

    If the object is sliced, as I believe, you'll see the wrong bits with the created `Foo' living a very short life.
    [/Edit]

    So, um... congratulations?

    Soma
    Last edited by phantomotap; 12-16-2013 at 02:48 PM.
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  7. #7
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    Yeah, it looks like a bug in gcc's implementation of initializer lists, and what you describe is a reasonable explanation of what gcc's doing.

    But my point in bringing this code up was that you're calling a non-virtual destructor via a reference to the base class. You have a reference to a Bar object, but Foo's destructor is ultimately called. This also works if the temporary is returned from a function. It also works if Bar is non-instantiate-able, but Foo is. I was speculating that this might have interesting obscure use cases, although I cannot think of any.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  8. #8
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    But my point in bringing this code up was that you're calling a non-virtual destructor via a reference to the base class.
    O_o

    No. I don't think you understand.

    You very definitely are not calling a non-virtual destructor via a reference to the base class.

    You are not in your example code, and you are not in my example code, and you would not be in any valid code.

    The code, from your example, is effectively:

    [Edit]
    I added some context for the sake of completeness.
    [/Edit]

    Code:
    #include <iostream>
    #include <string>
    struct Bar{
        Bar(){}
    };
    
    struct Foo: Bar {
        Foo(){}
      ~Foo(){std::cout<<"dtor\n"<<std::flush;}
    };
    
    void do_stuff(){
      std::cout << "do stuff\n";
    }
    
    void similar
    (
        const std::string &
    )
    {
    }
    
    int main(){
      //similar("test");
      //const Bar & example{Foo()};
      // `{Foo()}' in this context IS NOT a `Bar' object
      // `{Foo()}' in this context IS A constructor call
      // the compiler REQUIRES a `Bar' object
      // the result is similar to `similar("test");'
      // the `std::string' object is REQUIRED
      // "test" IS NOT a `std::string' object.
      Bar exampleTEMP((Foo()));
      const Bar & example(exampleTEMP);
      do_stuff();
      return 0;
    }
    Now, I can't stress this enough: THIS IS A BAD EXAMPLE!.

    However, it is basically what "GCC" is doing for your code so let us continue: the compiler is calling the `Foo' destructor via the anonymous `Foo' object, within the brace-enclosed `Foo();', and the compiler is calling the `Bar' destructor via the anonymous `Bar' object, but you are not calling a destructor from the named reference; the reference only gives you a handle to the anonymous `Bar' object which could not otherwise be used.

    In all cases considered here, the compiler knows the exact type of the anonymous object so calls the correct destructor regardless of what references you may have named.

    You have a reference to a Bar object, but Foo's destructor is ultimately called. This also works if the temporary is returned from a function. It also works if Bar is non-instantiate-able, but Foo is.
    Correct.

    However, those facts are completely unrelated to the code or the utility you think the code implies.

    The code you posted (post: #1), as fixed (post: #4) with parentheses, is exactly equivalent to:

    Code:
    #include <iostream>
    struct Foo{
      Foo(){}
      ~Foo(){std::cout<<"dtor\n"<<std::flush;}
    };
     
    void do_stuff(){
      std::cout << "do stuff\n";
    }
     
    int main(){
      Foo _ANONYMOUS_00;
      const Foo & example(_ANONYMOUS_00);
      do_stuff();
      return 0;
    }
    The facts you are referencing arise because the compiler is totally aware of the anonymous temporary and manages that object, a stack variable, exactly as it would any named object.

    [Edit]
    To be clear, the facts hold because the anonymous temporary must be managed in any event.

    In other words, whether you name the anonymous temporary or not the correct destructor must be called.

    Code:
    #include <string>
    
    std::string Go()
    {
        return("test");
    }
     
    int main()
    {
        {const std::string & s(Go());}
        {Go();}
    }
    The lines 10 and 11 are functionally identical.

    The only difference is the visibility of the temporary necessarily created by the call to `Go': the temporary has a name in line 10 while it remains anonymous in line 11.
    [/Edit]

    I was speculating that this might have interesting obscure use cases, although I cannot think of any.
    There is no obscure use cases because you aren't doing the thing you think you are doing.

    You are not calling a non-virtual method polymorphically from a diminished reference; the compiler is calling a method, regardless of virtual possibilities, of a known object managed by the stack.

    Soma
    Last edited by phantomotap; 12-16-2013 at 03:44 PM.
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  9. #9
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    I was aware of what the compiler is doing (except for the part that copying is allowed, and the gcc bug, which confused me). My question is if it's useful somehow.

    I guess you could describe it as c++98's version of c++11 auto variables. So that's a poor c++98 use case.

    EDIT:
    "You very definitely are not calling a non-virtual destructor via a reference to the base class."
    I am in the sense that a non virtual destructor is called, and all that's declared is a reference to the base class. You correctly describe the reason why this works, but the result is what it is -- there is no explicit base object reference, and the code needs inheritance to work.
    Last edited by King Mir; 12-16-2013 at 04:16 PM.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  10. #10
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    I guess you could describe it as c++98's version of c++11 auto variables. So that's a poor c++98 use case.
    O_o

    Well, yeah, you could, but you would be greatly exaggerating the utility.

    I am in the sense that a non virtual destructor is called, and all that's declared is a reference to the base class.
    No. You are not in any sense.

    The destructor would still be called even without the reference; you may as well argue you could call a non-virtual destructor via a completely unrelated reference because that would still work:

    Code:
    #include <iostream>
    struct Bar{
    };
    
    struct Foo: Bar {
      ~Foo(){std::cout<<"dtor\n"<<std::flush;}
    };
    
    const Foo * Awful
    (
        const Foo & f
    )
    {
        return(&f);
    }
    
    int main(){
      const unsigned int & x(*reinterpret_cast<const unsigned int *>(Awful(Foo())));
      return 0;
    }
    The `Foo' object is still destroyed correctly, but I am not, in any way, calling the destructor for the anonymous `Foo' object more or less than the example code you posted.

    Of course, the lifetime of the anonymous object isn't changed, but then, the introduction of the call changes how the compiler perceives the expected lifetime in any event:

    Code:
    #include <iostream>
    struct Bar{
    };
     
    struct Foo: Bar {
      ~Foo(){std::cout<<"dtor\n"<<std::flush;}
    };
     
    void do_stuff(){
      std::cout << "do stuff\n";
    }
    
    const Bar & Awful
    (
        const Foo & f
    )
    {
        return(f);
    }
     
    int main(){
      const Bar & example(  Awful( Foo() )  );
      do_stuff();
      return 0;
    }
    You correctly describe the reason why this works, but the result is what it is -- there is no explicit base object reference, and the code needs inheritance to work.
    Well, yes, there is an explicit base object reference, and while there is no explicit derived object reference, the inheritance is unimportant. In fact, you've only confused things by introducing inheritance:

    Code:
    #include <iostream>
    struct Bar{
    };
    
    struct Foo {
      ~Foo(){std::cout<<"dtor\n"<<std::flush;}
    };
    
    const Foo & Awful
    (
        const Foo & f
    )
    {
        return(f);
    }
    
    int main(){
      const Bar & x(reinterpret_cast<const Bar &>(Awful(Foo())));
      return 0;
    }
    Here again the `Foo' temporary is destroyed correctly, but that fact is completely unrelated to the named reference.

    The named reference from the correct and valid code does two things: names the temporary and tells the compiler to extend the lifetime of the object to that of the reference.

    The very notion of "calling a non-virtual destructor via a reference to the base class" is dangerous, and you are not doing that, and you should not do that because it doesn't work:

    Code:
    #include <iostream>
    struct Bar{
    };
     
    struct Foo: Bar {
      ~Foo(){std::cout<<"dtor\n"<<std::flush;}
    };
     
    int main(){
      Bar * example(new Foo);
      delete example;
      return 0;
    }
    The mechanism here is broken, and the mechanism would still be broken if the compiler approached the anonymous temporary as if by reference to the base class which it does not do because you are only naming the temporary that compiler would have already managed.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  11. #11
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    What you describe is all true, but I stand by my description of the result.

    In any case, however it's described, I'm looking to get feedback on if it's used. You answered that in your first post.

    My reason for asking actually from the perspective of language design. I understand that the way C++ is described, a const reference needs to work this way so that function parameters work, but there is extra logic that's needed in the non-parameter case, and I want to know if doing so is useful.
    Last edited by King Mir; 12-16-2013 at 06:34 PM.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Const pointers/references
    By +Azazel+ in forum C++ Programming
    Replies: 11
    Last Post: 09-15-2009, 03:07 AM
  2. Cannot add const to references?
    By Elysia in forum C++ Programming
    Replies: 2
    Last Post: 07-14-2008, 06:09 AM
  3. Const and References
    By vb.bajpai in forum C++ Programming
    Replies: 4
    Last Post: 06-20-2007, 04:48 PM
  4. const references initialized to a literal
    By Mario F. in forum C++ Programming
    Replies: 3
    Last Post: 05-29-2006, 08:52 AM
  5. Windows Update temporaries...
    By xErath in forum A Brief History of Cprogramming.com
    Replies: 10
    Last Post: 11-14-2004, 12:51 PM