0

If one creates a const reference to a temporary, its life is extended as if the reference where in the stack. It is a good feature of the language, although it is presented sometimes like an exception to other rules. https://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/

However this doesn't work when the const reference is part the member of a class. Is this an inconsistency of the language?

Example code:

int f(int a){return a + 5;}
int main(){
    int const& b = f(2);
    assert(b == 7); // ok, not a dangling reference

    struct single{
        int const& m_;
        single(int const& m) : m_(m){}
    };
    single s{f(3)};
    assert(s.m_ == 8); // fails, dangling reference

    struct pair{
        int const& m1_;
        int const& m2_;
        pair(int const& m1, int const& m2) : m1_(m1), m2_(m2){}
    };

    pair p{f(3), f(4)};
    assert( p.m1_ == 8 ); // fails, dangling reference
}

Is there a workaround for this to work or at least behave more consistently?

I found this to be a limiting factor in a few contexts now. For example, List using with references, changes behavior when used as a member and https://stackoverflow.com/a/51878764/225186


EDIT1: In other answers to similar questions is mentioned that the problem is that the constructor takes a const& where the rule doesn't apply. However a perfect forward still fails. In this case, either the inconsistency in the language is more evident or perfect forward is not as perfect.

struct single{
    int const& m_;
    template<class T>
    single(T&& m) : m_(std::forward<T>(m)){}
};

EDIT2: Declaring single const& s{f(3)}; still doesn't help. However "moving" the constness to the structure helps.

struct single{
    int m_; // no const!
    template<class T>
    single(T&& m) : m_(std::forward<T>(m)){}
};
...
single const& s{f(3)}; // const ref with extended lifetime

So, perhaps it is good practice to transfer the constness to the whole struct.

I still think that reference members behave weirdly in the language. https://www.youtube.com/watch?v=uYDt1gCDxhM


EDIT3: As @Oliv mentions, the situation improves if one uses agregate initialization. However this is quite limiting.

struct single{
    int const& m_;
};
...
single s{f(3)};
assert(s.m_ == 5);
alfC
  • 14,261
  • 4
  • 67
  • 118
  • 1
    Unfortunately the duplicate mark does not lead you to the right answer. The language consistency involved is that when a temporary is bound to a reference function argument, the "binding" is released at the end (of the full expression involving) the function call. Constructor call is considered a function call. To avoid that you can use aggregate initialization as described in the first SO answer you cite. Aggregate initialization does not involve binding the temporary to a function reference arguement. – Oliv Aug 18 '18 at 07:54
  • Second remark, temporary life time expansion is not propagated when you "bind a reference to an other reference." – Oliv Aug 18 '18 at 07:57
  • 1
    A very good answer on the subject: https://stackoverflow.com/a/23965233/5632316 – Oliv Aug 18 '18 at 08:10
  • The first duplicate answers the question with a standard quote (the standard explicitly says that constructors don't extend lifetime beyond the constructor call) – M.M Aug 18 '18 at 08:39

0 Answers0