6
votes

I'm using N3936 as a reference here (please correct this question if any of the C++14 text differs).

Under 3.10 Lvalues and rvalues we have:

Every expression belongs to exactly one of the fundamental classifications in this taxonomy: lvalue, xvalue, or prvalue.

However the definition of lvalue reads:

An lvalue [...] designates a function or an object.

In 4.1 Lvalue-to-rvalue conversion the text appears:

[...] In all other cases, the result of the conversion is determined according to the following rules: [...] Otherwise, the value contained in the object indicated by the glvalue is the prvalue result.

My question is: what happens in code where the lvalue does not designate an object? There are two canonical examples:

Example 1:

int *p = nullptr;
*p;
int &q = *p;
int a = *p;

Example 2:

int arr[4];
int *p = arr + 4;
*p;
int &q = *p;
std::sort(arr, &q);

Which lines (if any) are ill-formed and/or cause undefined behaviour?

Referring to Example 1: is *p an lvalue? According to my first quote it must be. However, my second quote excludes it since *p does not designate an object. (It's certainly not an xvalue or a prvalue either).

But if you interpret my second quote to mean that *p is actually an lvalue, then it is not covered at all by the lvalue-to-rvalue conversion rules. You may take the catch-all rule that "anything not defined by the Standard is undefined behaviour" but then you must permit null references to exist, so long as there is no lvalue-to-rvalue conversion performed.

History: This issue was raised in DR 232 . In C++11 the resolution from DR232 did in fact appear. Quoting from N3337 Lvalue-to-rvalue conversion:

If the object to which the glvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior.

which still appears to permit null references to exist - it only clears up the issue of performing lvalue-to-rvalue conversion on one. Also discussed on this SO thread

The resolution from DR232 no longer appears in N3797 or N3936 though.

2
I think the intent is that whether an expression is an lvalue or not is determinable at compile time. So given int *p = nullptr;, the expression *p is an lvalue even though it doesn't currently designate an object; evaluating it causes undefined behavior. Look at the definitions of lvalue in the C90, C99, and C11 standards; it took the C committee a couple of decades to get the definition right. (Under the C99 definition, taken literally, 42 is an value and evaluating it causes undefined behavior -- clearly not the intent.)Keith Thompson
@KeithThompson so you don't restrict evaluating it to necessitating an lvalue-to-rvalue conversion?M.M
@ShafikYaghmour thanks for those links. I'm inclined to treat the "value category" clause as being defective. But I'm not sure what the fix is. In C11 they say *p is an lvalue but it causes UB if it does not designate an object when evaluated, except for the special case &*p. In C++ there is not such a simple solution because of the possibility of taking a reference.M.M
There is a note in section 8.3.1 Pointers which says Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by indirection through a null pointer, which causes undefined behavior. ... That note has been there since at least N1804Shafik Yaghmour

2 Answers

2
votes

It isn't possible to create a reference to null or a reference to the off-the-end element of an array, because section 8.3.2 says (reading from draft n3936) that

A reference shall be initialized to refer to a valid object or function.

However, it is not clear that forming an expression with a value category of lvalue constitutes "initialization of a reference". Quite the contrary, in fact, temporary objects are objects, and references are not objects, so it cannot be said that *(a+n) initializes a temporary object of reference type.

1
votes

I think the answer to this although probably not the answer you really want, is that this is under-specified or ill-specified and therefore we can not really say whether the examples you have provided are ill-formed or invoke undefined behavior according the current draft standard.

We can see this by looking DR 232 and DR 453.

DR 232 tells us that the standard conflicts on whether derferencing a null pointer is undefined behavior:

At least a couple of places in the IS state that indirection through a null pointer produces undefined behavior: 1.9 [intro.execution] paragraph 4 gives "dereferencing the null pointer" as an example of undefined behavior, and 8.3.2 [dcl.ref] paragraph 4 (in a note) uses this supposedly undefined behavior as justification for the nonexistence of "null references."

However, 5.3.1 [expr.unary.op] paragraph 1, which describes the unary "*" operator, does not say that the behavior is undefined if the operand is a null pointer, as one might expect. Furthermore, at least one passage gives dereferencing a null pointer well-defined behavior: 5.2.8 [expr.typeid] paragraph 2 says

and introduces the concept of an empty lvalue which is the result of indiretion on a null pointer or one past the end of an array:

if any. If the pointer is a null pointer value (4.10 [conv.ptr]) or points one past the last element of an array object (5.7 [expr.add]), the result is an empty lvalue and does not refer to any object or function.

and proposes that the lvaue-to-rvalue conversion of such is undefined behavior.

and DR 453 tell us that we don't know what a valid object is:

What is a "valid" object? In particular the expression "valid object" seems to exclude uninitialized objects, but the response to Core Issue 363 clearly says that's not the intent.

and suggests that binding a reference to an empty value is undefined behavior.

If an lvalue to which a reference is directly bound designates neither an existing object or function of an appropriate type (8.5.3 [dcl.init.ref]), nor a region of memory of suitable size and alignment to contain an object of the reference's type (1.8 [intro.object], 3.8 [basic.life], 3.9 [basic.types]), the behavior is undefined.

and includes the following examples in the proposal:

int& f(int&);
int& g();

extern int& ir3;
int* ip = 0;

int& ir1 = *ip;     // undefined behavior: null pointer
int& ir2 = f(ir3);  // undefined behavior: ir3 not yet initialized
int& ir3 = g();
int& ir4 = f(ir4);  // ill-formed: ir4 used in its own initializer

So if we want to restrict ourselves to dealing only with the intent then I feel that DR 232 and DR 453 provide the information we need to say that the intention is that lvalue-to-rvalue conversion of a null pointer is undefined behavior and a reference to a null pointer or an indeterminate value is also undefined behavior.

Now although it has taken a while for both of these report resolutions to be sorted out, they are both active with relatively recent updates and apparently the committee so far does not disagree with the main premise that the defects reported are actual defects. So it follows without knowing these two items it would imply it is not possible to provide an answer to your question using the current draft standards.