Spurious Warning About Binding Temporary to Reference Member in Constructor

Spurious warning about binding temporary to reference member in constructor

This is a bug in gcc 4.8 that has been fixed in 4.9. Here is the bug report:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=50025

Lifetime of a temporary object bound to a reference member in member initializer list (C++14)

The defect report you cited has been adopted, and N4582 already includes the new wording in [class.base.init]:

A temporary expression bound to a reference member in a mem-initializer is ill-formed. [Example:

struct A {
A() : v(42) { } // error
const int& v;
};

— end example ]

So it's not extended for the object's lifetime - the code is simply ill-formed. gcc and clang both emit warnings on your code on every version I've tried, which I think is conforming but ideally they should error there.

Does binding temporary to a reference require a copy constructor in C++?

So is it necessary to have a copy constructor accessible when binding a temporary to a reference?

Post C++11 - No

Pre C++11 - Yes.


This code compiles fine with GCC 4.7.2 because it is compliant with the C++11 standard.

C++11 standard mandates that when a const reference is initialized from prvalue, it must be bound directly to the reference object and no temporary is permitted to be created. Also, the copy constructor is not used or required.

Prior to C++11 the rules were different. And this behavior(whether copy constructor will be called) is implementation defined. C++03 allowed the copy constructor being called while binding a const reference to an temporary and hence post C++11 the copy constructor needs to be accessible. Visual C++2010 adheres to the C++03 standard.

Why does passing a reference through a conditional operator to an initializer list lead to a warning?

If you add Wextra flag you will get this warning:

px.cpp:12:83: warning: a temporary bound to ‘ReferencingContainer::_vector’ only persists until the constructor exits [-Wextra]
, _vector(container._vector.size() > 0 ? container._vector : throw "error") // <--- warning occurs here!

which is a bug of the compiler, according to this question:

Spurious warning about binding temporary to reference member in constructor

However, as vsoftco pointed out, the warning persists in version 5.1

Other relevant questions:

  1. C++ constructor: garbage while initialization of const reference
  2. Does a const reference prolong the life of a temporary?

A possible quick fix is to pass a pointer out of the ?: expression and then dereference it.

, _vector(*(container._vector.size() > 0 ? &container._vector : throw "error"))

So what's the issue here?

Compiler tells you, usually they are polite to us. By the ref of std::vector::size, we have:

size_type size() const;

which means that when you call it in the line it creates the warning, size() will return a copy of the size of _vector, which will out of scope when the constructor goes out of scope.

Initialization of const reference member in initializer list

GCC is correct in its interpretation of {}. [dcl.init.list]/p3.8-9 (quoting N4296; earlier drafts has the same relative ordering of these two bullets):

List-initialization of an object or reference of type T is defined as
follows:

  • [7 inapplicable bullets omitted]

  • Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is copy-list-initialized or
    direct-list-initialized, depending on the kind of initialization for
    the reference, and the reference is bound to that temporary. [ Note:
    As usual, the binding will fail and the program is ill-formed if the
    reference type is an lvalue reference to a non-const type. —end note
    ]

  • Otherwise, if the initializer list has no elements, the object is value-initialized.

List-initializing the reference hits bullet 3.8, causing the construction of a temporary. The value-initialization case, in 3.9, doesn't apply.

Value-initialization of a reference is ill-formed ([dcl.init]/p9):

A program that calls for default-initialization or
value-initialization of an entity of reference type is ill-formed.


However, as of N4296, per [class.base.init]/p8:

A temporary expression bound to a reference member in a
mem-initializer is ill-formed.

This was added as a result of CWG issue 1696, which is a DR (defect report) against C++14.

Pre-CWG1696, the standard provided that (N4140 [class.temporary]/p5.1):

A temporary bound to a reference member in a constructor’s
ctor-initializer (12.6.2) persists until the constructor exits.

which means that the reference will become dangling immediately after construction. This presumably motivated CWG1696's decision to disallow such bindings altogether.

Warning C4172: Returning a reference to const std::string bound to a local variable. How safe is it?

Yes it is not safe.

Returning address of a local variable or temporary and dereferencing it results in Undefined Behavior.

As you commented:

Yes, the lifetime of the temporary bound to a constant reference increases till the lifetime of constant. But that needs the caller to accept the return value in a const reference, So by itself the function won't be safe.

From the C++ Standard:

C++03 12.2 Temporary objects:

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) persists until the constructor exits. A temporary bound to a reference
parameter in a function call (5.2.2) persists until the completion of the full expression containing the call.A temporary bound to the returned value in a function return statement (6.6.3) persists until the function exits

Disallowing creation of the temporary objects

Edit: As j_random_hacker notes, it is possible to force the user to declare a named object in order to take out a lock.

However, even if creation of temporaries was somehow banned for your class, then the user could make a similar mistake:

// take out a lock:
if (m_multiThreaded)
{
CSingleLock c(&m_criticalSection, TRUE);
}

// do other stuff, assuming lock is held

Ultimately, the user has to understand the impact of a line of code that they write. In this case, they have to know that they're creating an object and they have to know how long it lasts.

Another likely mistake:

 CSingleLock *c = new CSingleLock(&m_criticalSection, TRUE);

// do other stuff, don't call delete on c...

Which would lead you to ask "Is there any way I can stop the user of my class from allocating it on the heap"? To which the answer would be the same.

In C++0x there will be another way to do all this, by using lambdas. Define a function:

template <class TLock, class TLockedOperation>
void WithLock(TLock *lock, const TLockedOperation &op)
{
CSingleLock c(lock, TRUE);
op();
}

That function captures the correct usage of CSingleLock. Now let users do this:

WithLock(&m_criticalSection, 
[&] {
// do stuff, lock is held in this context.
});

This is much harder for the user to screw up. The syntax looks weird at first, but [&] followed by a code block means "Define a function that takes no args, and if I refer to anything by name and it is the name of something outside (e.g. a local variable in the containing function) let me access it by non-const reference, so I can modify it.)

Extending the lifetime of a temporary object without copying it

Why is the lifetime of object x not extended past the function call even if a const reference has been bound to it?

Technically, the lifetime of the object is extended past the function call. It is not however extended past the initialization of wrap. But that's a technicality.

Before we dive in, I'm going to impose a simplification: let's get rid of wrapper. Also, I'm removing the template part because it too is irrelevant:

const object &function(const object &arg)
{
return arg;
}

This changes precisely nothing about the validity of your code.

Given this statement:

const object &obj = function(object{}); // Let's call that temporary object x

What you want is for the compiler to recognize that "object x" and obj refer to the same object, and therefore the temporary's lifetime should be extended.

That's not possible. The compiler isn't guaranteed to have enough information to know that. Why? Because the compiler may only know this:

const object &function(const object &arg);

See, it's the definition of function that associates arg with the return value. If the compiler doesn't have the definition of function, then it cannot know that the object being passed in is the reference being returned. Without that knowledge, it cannot know to extend x's lifetime.

Now, you might say that if function's definition is provided, then the compiler can know. Well, there are complicated chains of logic that might prevent the compiler from knowing at compile time. You might do this:

const object *minimum(const object &lhs, const object &rhs)
{
return lhs < rhs ? lhs : rhs;
}

Well, that returns a reference to one of them, but which one will only be determined based on the runtime values of the object. Whose lifetime should be extended by the caller?

We also don't want the behavior of code to change based on whether the compiler only has a declaration or has a full definition. Either it's always OK to compile the code if it only has a declaration, or it's never OK to compile the code only with a declaration (as in the case of inline, constexpr, or template functions). A declaration may affect performance, but never behavior. And that's good.

Since the compiler may not have the information needed to recognize that a parameter const& lives beyond the lifetime of a function, and even if it has that information it may not be something that can be statically determined, the C++ standard does not permit an implementation to even try to solve the problem. Thus, every C++ user has to recognize that calling functions on temporaries if it returns a reference can cause problems. Even if the reference is hidden inside some other object.

What you want cannot be done. This is one of the reasons why you should not make an object non-moveable at all unless it is essential to its behavior or performance.



Related Topics



Leave a reply



Submit