Binding const& of temporary: No compiler warning?

822 views Asked by At

I have a TestClass with a const& member variable. I know from various places and own experiences that it is a bad idea to initialize this const& with the reference to a temporary value. So I was quite suprised that the following code will compile fine (tested with gcc-4.9.1, clang-3.5, and scan-build-3.5) but fail to run properly.

class TestClass {
  public:
    // removing the "reference" would remove the temporary-problem
    const std::string &d;

    TestClass(const std::string &d)
        : d(d) {
        // "d" is a const-ref, cannot be changed at all... if it is assigned some
        // temporary value it is mangled up...
    }
};

int main() {

    // NOTE: the variable "d" is a
    // temporary, whose reference is not valid... what I don't get in the
    // moment: why does no compiler warn me?
    TestClass dut("d");

    // and printing what we got:
    std::cout << "beginning output:\n\n";
    // this will silently abort the program (gcc-4.9.1) or be empty
    // (clang-3.5) -- don't know whats going on here...
    std::cout << "dut.d: '" << dut.d << "'\n";
    std::cout << "\nthats it!\n";

    return 0;
}

Why does none of the two compilers warn me at compile-time? See also this ideone, with some more testing going on.

4

There are 4 answers

8
Alex Gidan On BEST ANSWER

No warning as no offense:

local const references prolong the lifespan of the variable.

The standard specifies such behavior in §8.5.3/5, [dcl.init.ref], the section on initializers of reference declarations. The lifetime extension is not transitive through a function argument. §12.2/5 [class.temporary]:

The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object to a subobject of which the temporary is bound persists for the lifetime of the reference except as specified below. A temporary bound to a reference member in a constructor’s ctor-initializer (§12.6.2 [class.base.init]) persists until the constructor exits. A temporary bound to a reference parameter in a function call (§5.2.2 [expr.call]) persists until the completion of the full expression containing the call.

You can have a look at gotw-88 for an extended and more readable discussion on this topic.

Undefined Behaviour

So is it your code correct? Nope, and its execution will lead to undefined behaviour. The real problem in your code snapshot is that the Undefined Behaviour is caused by the mix of two perfectly legal operations: the call of the constructor passing a temporary object (whose life spans inside the constructor block) and the binding of the reference in the constructor definition.

The compiler is not smart enough to detect this explosive statement combination, so this is why you don't get any warning.

1
sjdowling On

Binding a const & to a temporary is valid and the compiler will ensure that the temporary will live at least as long as the reference. This allows you to do things like pass string literals into functions expecting a const std::string &.

In your case however you are copying that reference and thus the lifetime guarantee no longer holds. Your constructor exits and the temporary is destroyed and you are left with a reference to invalid memory.

0
ravi On
TestClass(const std::string &d1)
    : d(d1) {

TestClass dut("d");

I guess following is happening logically:-

1) Your string literal ("d") would be implicitly converted to std::string ( Let's give it a name 'x' ).

2) So, 'x' is a temporary which is bound to d1 here. Lifetime of this temporary is extended to lifetime of your d1. Although that string literal would always be alive till the end of program.

3) Now you're making 'd' refer to 'd1'.

4) At the end of your constructor d1's lifetime is over and so is d's.

All compiler's are not so clever to figure out these minor glitches...

4
Angew is no longer proud of SO On

The problem is that there is no single point in which a warning would be warranted. It's only the combination of the call of the constructor and its implementation that leads to Undefined Behaviour.

If you consider just the constructor:

class TestClass {
  public:
    const std::string &d;

    TestClass(const std::string &d)
        : d(d)
    {}
};

There's nothing wrong here, you got a reference and you're storing one. Here's an example of perfectly valid use:

class Widget {
  std::string data;
  TestClass test;

public:
  Widget() : data("widget"), test(data)
  {}
};

If you consider just the call site:

//Declaration visible is:
TestClass(const std::string &d);

int main() {
    TestClass dut("d");
}

Here, the compiler doesn't "see" (in the general case) the definition of the constructor. Imagine an alternative:

struct Gadget {
  std::string d;

  Gadget(cosnt std::string &d) : d(d) {}
};

int main()
{
  Gadget g("d");
}

Surely you wouldn't want a warning here either.

To summarise, both the call site and the constructor implementation are perfectly usable as-is. It's only their combination which causes issues, but that combination is beyond the context a compiler can reasonably use to emit warnings.