0

Like, if I write code that looks like the following:

const int & const_value = 10;

What advantage am I gaining over code that would instead look like this:

int value = 10;

or

const int const_value = 10;

Even for a more complicated example, like

const enterprise::complicated_class_which_takes_time_to_construct & obj = factory.create_instance();

Thanks to copy elision, both of the following code snippets shouldn't be significantly faster or less memory consuming.

enterprise::complicated_class_which_takes_time_to_construct obj = factory.create_instance();

or

const enterprise::complicated_class_which_takes_time_to_construct obj = factory.create_instance();

Is there something I'm missing that explains the use of this construct, or a hidden benefit I'm not accounting for?

Edit:

The answer provided by @nwp supplied the example of a factory method that looks like the following:

using cc = enterprise::complicated_class_which_takes_time_to_construct;
struct Factory{
    const cc &create_instance(){
        static const cc instance;
        return instance;
    }
} factory;

While this is a good example of why you'd need the exact const reference construct in general, it doesn't answer why it would be needed for dealing with temporaries, or why this particular construct would be better than just seating the temporary in a proper stack-allocated object, which the compiler should still be able to copy-elide in normal situations.

Community
  • 1
  • 1
Xirema
  • 19,889
  • 4
  • 32
  • 68
  • _"What advantage am I gaining over code that would instead look like this ..."_ Well, you prevent any code trying to change it, when that's not intended? – πάντα ῥεῖ Jun 01 '16 at 19:22
  • Your first example has no sense and there is no reason to do it (there are reasons to not!). Your second example is different - it might make sense if copy constructor for your instance is not available. But the question is unclear. – SergeyA Jun 01 '16 at 19:23
  • @πάνταῥεῖ That doesn't really answer the crux of my question though; you could just declare the object `const` without making it a `const reference` if that was all you needed. Is there something beyond that that yields a benefit by making it a `const reference`? – Xirema Jun 01 '16 at 19:24
  • 1
    It is unclear to me exactly what you want. It really depends on what you need to do with the temporary after you have it. – NathanOliver Jun 01 '16 at 19:24
  • Voted to reopen since it's a clear simple question that hasn't been answered (the existing single attempted answer is not relevant). – Cheers and hth. - Alf Jun 01 '16 at 19:59
  • Possible duplicate: (http://stackoverflow.com/questions/20546981/what-is-the-rationale-for-extending-the-lifetime-of-temporaries/24895239#24895239), but it's not *quite* fitting this question. – Cheers and hth. - Alf Jun 01 '16 at 20:03
  • @Xirema it is an important difference as to whether the factory method returns by value or by reference, your question should cover this (preferably show the actual factory method) – M.M Jun 01 '16 at 21:24
  • The `10` case has nothing to do with the factory case – M.M Jun 01 '16 at 21:24
  • 1
    suggest using a shorter name than `complicated_class_which_takes_time_to_construct` so readers can read the code in the question without having to scroll multiple code boxes – M.M Jun 01 '16 at 21:25
  • `const auto&` allow to uniformly takes return value without have to worry if it is a temporary or a const reference. So implementation may change without to create an extra copy if return value change from value to const reference. – Jarod42 Jun 01 '16 at 22:32
  • @M.M: Your comments are only meaningful for an incorrect and mostly meaningless interpretation of the question. It's not about using reference to `const`. It's about using that to create a lifetime extension of a temporary. Check the title. For example. – Cheers and hth. - Alf Jun 02 '16 at 08:07
  • So is your question about `int`? Or about `const ClassType& x = ClassType();`? These are different, so you should clarify – Johannes Schaub - litb Jun 02 '16 at 15:59

3 Answers3

2

Imagine the factory is "clever" and does something like

using cc = enterprise::complicated_class_which_takes_time_to_construct;
struct Factory{
    const cc &create_instance(){
        static const cc instance;
        return instance;
    }
} factory;

and further assume cc is not copyable then the const & version works while the other 2 don't. This is fairly common for classes that implement a cache and keep a local map of objects and you get a reference to the object inside the map.

Edit:

It is possible a coding guideline would say Take return values from functions by const &, because that works universally with values and references.

cc foo();
cc &foo();
const cc &foo();

If you use const cc &var = foo(); it will work without an unnecessary copy in all the cases. Sometimes you get a temporary, sometimes you don't, but the code always does the right thing, freeing you to change the implementation and to not having to care about how the function you use returns its return-value. There is still the issue of having a const in there, so it is not the superior notation in all cases, but it is a viable design choice.

nwp
  • 9,623
  • 5
  • 38
  • 68
  • So if I'm understanding correctly, it's broadly a solution for factory methods for classes which don't have copy or move constructors? – Xirema Jun 01 '16 at 19:28
  • 1
    @Xirema Either that or they are expensive to create, so you want to make sure you only create them once. – nwp Jun 01 '16 at 19:29
  • Right, because in the specific situation you described, it would be impossible to copy-elide the result. Alright. That makes sense. I'll accept the answer when the system lets me. – Xirema Jun 01 '16 at 19:31
  • 2
    **−1** This does not address the question of "holding a Temporary". The case in this attempted answer is not about lifetime extension of a temporary. – Cheers and hth. - Alf Jun 01 '16 at 19:57
  • +1, the question is "What problems are solved by holding a Temporary using `const T &`?" , and this answer describes one such problem. – M.M Jun 02 '16 at 06:46
  • 1
    @M.M Can you please clarify which **temporary** is held by a `const T &` in this case? – Angew is no longer proud of SO Jun 02 '16 at 07:17
  • @Angew I added a temporary. I thought the answer would be valid just explaining the encountered syntax choice without an actual temporary, but apparently people disagree. – nwp Jun 02 '16 at 07:42
  • The question is specifically about temporaries, so showing a case without a temporary is off. But the consistency of accepting return values is a very good argument. s/-1/+1/ for me. – Angew is no longer proud of SO Jun 02 '16 at 07:46
  • @Angew: Rather than being a plus, that "consistency" will generally introduce bugs, inadvertently referring to a global object whose value is in the habit of changing. However, unfortunately **there's no way to downvote this more than once**. But summing up, it's an attempted answer that is (1) irrelevant, has nothing to do with the question, and (2) gives very very dangerous advice, an open invitation to a bug infestation. We need a way to say "This answer has very large negative value". Downvote doesn't cover it. – Cheers and hth. - Alf Jun 02 '16 at 07:57
2

There's one "pathological" case that comes to mind: non-copyable and non-movable objects. Such an object cannot be stored by value, so holding it by a reference is your only chance:

#include <iostream>

struct Immovable
{
    Immovable(int i) : i(i) {}

    Immovable(const Immovable&) = delete;
    Immovable(Immovable&&) = delete;

    const int i;
};


Immovable factory()
{
    return {42};
}


int main()
{
    const Immovable &imm = factory();
    std::cout << imm.i;
}

[Live example]

On a less contrived note, you dismissed the "heavy object" optimisation by citing copy elision. Note, however, that copy elision is optional. You can never guarantee that the compiler will do it. Storing the temporary in a const &, on the contrary, guarantees that no copying or moving will happen.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
2

The rationale for the lifetime extension rule is known ¹(because Bjarne Stroustrup, the language creator, said so) to be to have uniform simple rules. In particular, the lifetime of a temporary used as actual argument in a function call, is extended to the end of the full-expression, covering the lifetime of any reference that it's bound to. And the rule for local reference to const, or local rvalue reference, makes that aspect of the behavior the same.


In C++03 one practical use case, as far as I know ²invented by Petru Marginean, was to create a local object of an unknown automatically deduced type, like this:

Base const& o = foo( arg1, arg2, arg3 );

This works when Base is an accessible base class of any possible foo result type, because the lifetime extension mechanism does not slice: it extends the lifetime of the full temporary object. When foo returns a T it's a complete T object that has its lifetime extended. The compiler knows T even though the programmer may not necessarily know T.

Of course, in C++11 and later one can instead, generally, use auto:

 auto const o = foo( arg1, arg2, arg3 );

With return value optimization and/or moving this will be just as efficient as Marginean's trick, and it's more clear and simple.


However, when the type T is non-movable and non-copyable, then binding to a reference with lifetime extension of the temporary, appears to be the only way to hold on to it, which is a second use case.


Now, surprise?, the type T of the full temporary object needs not be derived from the statically known type. It's enough that compiler knows that you're holding a reference to a part of the temporary. Whether that part is a base class sub-object, or some other sub-object, doesn't matter, so you can do:

auto const& part = foo( arg1, arg2, arg3 ).a_part;

Which is a third use case, which is only about not introducing an otherwise never used name for the complete object, keeping the code simple.


Standardese about the hold-on-to-a-part (and thus the whole object) case:

C++15 §12.2/5 [class.temporary]

The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except …

The exceptions include that a temporary used as actual argument in a function call, persists till the end of the full-expression, i.e. a bit longer than any formal argument reference that it's bound to.


¹ In an exchange with me in the Usenet group comp.lang.c++.
² In the C++03 implementation of the ScopeGuard class, which was invented by Petru. In C++11 a ScopeGuard can be trivially implemented using std::function and auto in the client code's dceclaration.

Community
  • 1
  • 1
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331