Lvalues which do not designate objects in C++14

2020-02-26 11:17发布

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条回答
够拽才男人
2楼-- · 2020-02-26 11:44

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.

查看更多
倾城 Initia
3楼-- · 2020-02-26 12:08

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.

查看更多
登录 后发表回答