I am having difficulty interpreting bullet point (5.2.1.1) in item 8.5.3 / 5 N4140

The snippet below compiles

#include <iostream>
int& f() { static int i = 100; std::cout << i << '\n'; return i; }

int main()
{
    int& r = f();
    r = 101;
    f();
}

      

and print the values โ€‹โ€‹( live example )

100
101

      

Now, after reading ยง8.5.3 / 5 in N4140, I see that it compiles because of the marker point (5.1.1), that is, the reference is an lvalue reference, the initializer expression is an lvalue and int

is reference-compatible with int

(or with int&

- I don't know exactly which one I should be using here).

Boolean points (5.1) and (5.1.1):

- If the reference is an lvalue reference and an initializer expression

 โ€” is an lvalue (but is not a bit-field), and "cv1 T1" is reference-compatible with
   "cv2 T2," or ...

      

Now suppose I change the reference to the left value in the declaration to int& r = f();

reference the correct value, i.e. int&& r = f();

... I know the code won't compile since the rvalue reference is not bound to an lvalue. But I'm wondering how to achieve this conclusion using the Standard?

I will explain what my difficulties are:

  • Clearly what is int&& r = f();

    covered by the marker (5.2) because the reference is an rvalue reference.

Bullet point (5.2):

- Otherwise, the reference must be an lvalue reference of a non-volatile const type (i.e., cv1 must be const), or the reference must be an rvalue reference.

  1. Basically, I would say that (5.2.1.1) supports this initialization, since the initializer is an lvalue function, and int

    is a int

    (or with int&

    ) compliant reference .

Boolean points (5.2.1) and (5.2.1.1):

- If the initializer expression

โ€” is an xvalue (but not a bit-field), class prvalue, array prvalue or function
  lvalue and "cv1 T1" is reference-compatible with "cv2 T2", or ...

      

Edit

I have included the bullet lines verbatim from N4140 (C ++ 14), which are equivalent to similar markers in N3337 (C ++ 11).

+3


source to share


1 answer


the initializer expression is an lvalue and int

is referential-compatible with int

(or with int&

- I don't know exactly which one I should be using here).

Reference-compatibility is a relationship that applies to the specified type, not to the reference type. For example, [dcl.init.ref] / 5 talks about initializing "a reference to type cv1 with an T1

expression of type cv2 T2

" and then compares, for example, "where T1

not referenced to T2

".

The expression type f()

is equal to everything int

, even though the return type f

is equal int&

. Expressions simply don't have a reference type when we observe them (*) ; the link is split and used to define the value category (see [expr] / 5). For int& f()

expression f()

is an lvalue; for int g()

expression g()

is the value of r.

(*) To be precise, expressions can be of a reference type in the standard , but only as an "initial" result type. The reference is discarded "until any further analysis", which means that this reference position is simply not observed across the type.




Now suppose I change the reference to the left value in the declaration to int& r = f();

reference the correct value, i.e. int&& r = f();

... I know the code won't compile since the rvalue reference is not bound to an lvalue. But I'm wondering how to achieve this conclusion using the Standard?

The confusion, as it seems from the discussion in the comments, seems to be f()

not an lvalue function. Value categories such as "lvalue" and "rvalue" are properties of expressions. Therefore, the term "lvalue function" must refer to an expression, namely a function type expression with the "lvalue" value category.

But an expression f()

is a function expression of a function . Grammatically it is a postfix expression, postfix is โ€‹โ€‹a list of function arguments. According to [expr.call] / 10:

A function call is an lvalue if the result type is an lvalue reference type or an rvalue reference for a function type, xvalue if the result type is an rvalue reference for an object type, and a prvalue otherwise.

And [expr.call] / 3

If postfix-expression denotes a destructor [...]; otherwise, the type of the function call expression is the return type of the statically selected function [...]

That is, (observed above ) the expression type f()

is equal int

and the value category is "lvalue". Note that the type (observable) is not int&

.

An lvalue function is, for example, an id expression like this f

, the result of an indirect use of a function pointer, or an expression giving any function reference:

using ft = void();
void f();
ft&  l();
ft&& r();
ft*  p();

// function lvalue expressions:
f
l()
r()
*p()

      

[expr.prim.general] / 8 indicates that those identifiers, like f

, are, like id expressions, lvalues:

An identifier is an id expression if it has been declared appropriately. [...] The type of the expression is the type of the identifier. The result is the object indicated by the identifier. The result is an lvalue if the object is a function, variable, or data item, and a prvalue otherwise.




Let's go back to the example int&& r = f();

. Using some post-N4296 project.

[dcl.init.ref]

5 A reference to type "cv1 T1

" is initialized with an expression of type "cv2 T2

" as follows:

  • (5.1) If the reference is an lvalue reference and an initializer expression

A link is an rvalue reference. 5.1 does not apply.

  • (5.2) Otherwise, the reference must be an lvalue reference to a non-volatile const type (i.e., cv1 must be const

    ), or the reference must be an rvalue reference. [example omitted]

This is applicable, the reference is an rvalue reference.

  • (5.2.1) If the initializer expression
    • (5.2.1.1) is an x โ€‹โ€‹value (but not a bitfield), a prvalue, an array prvalue, or an lvalue function and [...] or
    • (5.2.1.2) has a class type (ie T2

      - a class type) [...]

The initializer is an lvalue of type int

. 5.2.1 does not apply.

  • (5.2.2) Otherwise:
    • (5.2.2.1) If T1

      or T2

      is a class type [...]
    • (5.2.2.2) Otherwise, a temporary type "cv1 T1

      " is created and initialized with a copy (dcl.init) from the initializer expression. The link is then tied to a temporary one.

Finally, 5.2.2.2 applies. However:

If it T1

refers to T2

:

  • (5.2.2.3) cv1 must be the same cv qualification as or higher cv qualification than cv2; and
  • (5.2.2.4) if the reference is an rvalue reference, the initializer expression must not be an lvalue.

T1

and T2

are int

(the reference to the return type is f()

removed and is only used to define the value category), so they are bound to the binding. cv1 and cv2 are both empty. A reference is an rvalue reference, but f()

an lvalue, so 5.2.2.4 makes the program ill-formed.




The reason why the term "function lvalue" appears in 5.2.1.1, can be associated with the problem of "functions rvalues" (see, e.g.,. N3010 - References Rvalue as "funny" Lvalues ). There were no rvalue functions in C ++ 03 and it seems the committee didn't want to introduce them in C ++ 11. Without rvalue references, I find it impossible to get an rvalue function. For example, you cannot use a function type, and you cannot return function types from a function.

Probably for consistency, lvalues โ€‹โ€‹functions can be bound to rvalues โ€‹โ€‹of function type references using cast:

template<typename T>
void move_and_do(T& t)
{
    T&& r = static_cast<T&&>(t); // as if moved
}

int i = 42;
move_and_do(i);

move_and_do(f);

      

But for T

being a functional type void()

, the value category static_cast<T&&>(t)

is lvalue (no function type values). Therefore, rvalue references to function types can bind to lvalues โ€‹โ€‹functions.

+5


source







All Articles