c++gccclangtemporary-objects

c++ warning about indirectly taking reference to temporary


I found a bug in some code that was caused by taking a reference to a temporary object, albeit indirectly. The original author had expected this case to be covered by the rule that makes the reference valid (case 1). I instead had the expectation that a warning would be issued, as in case 2.

Instead, we got a bug with no warning from gcc in case 3 or 4, but I do get a warning from clang in the MWE below (tested on godbolt).

My question is whether the diagnostic is required by the c++ standard in case 2 and 3, 4, only in case 2, or not at all.

#include <iostream>
#include <vector>

std::vector<int> return_temporary() {
    std::vector<int> r{1,2,3,4,5,6,7,8,9};
    return r;
}

int main() {
    //case 1: correct, the const reference extends lifetime of the vector.
    const std::vector<int>& refvt = return_temporary();
    std::cout << "First element " << refvt.front() << '\n';
    //case 2: incorrect, flagged by reliable diagnostic from gcc and clang
    const std::vector<int>* ptrvt = &return_temporary();
    std::cout << "First element " << ptrvt->front() << '\n';
    //case 3: incorrect, unreliable diagnostic
    const int& refit = return_temporary().front();
    std::cout << "First element " << refit << '\n';
    //case 4: incorrect, unreliable diagnostic
    const int* ptrit = &return_temporary().front();
    std::cout << "First element " << ptrit << '\n';
}

Solution

  • It's difficult to give good warnings for this. Here's an example from the standard:

    struct S { int n; };
    auto f() {
        S x { 1 };
        constexpr S y { 2 };
        return [&](bool b) { return (b ? y : x).n; };
    }
    auto g = f();
    int m = g(false); // undefined behavior: access of x.n outside its lifetime
    int n = g(true);  // OK, does not access y.n
    

    So in this case, passing false as a parameter results in undefined behavior, but passing true does not.

    Worse, in this example we're assigning the result directly to an int, so the lvalue to rvalue conversion happens within the same expression where the dangling reference was returned. But in your examples, the reference is stored in a reference variable, and only later (in another expression) do we try to use the dangling reference, so the compiler is more likely to need to do some data flow analysis to assure that when you use the reference it's still certain to be dangling.

    As far as your question about warnings goes: I'm reasonably certain no diagnostic is required for any of these cases. At least as I read it, you have well formed code with undefined behavior. Undefined behavior doesn't require a diagnostic, only ill formed code does (and not even all ill formed code, but that's a separate question).