Assuming a class A has a member variable(which is an object rather than a reference) m. Naturally I will think that:
When I defined an object 'o1', then the Expression 'o1.m' is an object type either;
When I defined a reference 'q1', then the Expression 'q1.m' is a reference type either.
Is that conclusion correct? I'm wondering what any relative clauses are in C++ standard document if it is true(I've been searching for it/them for one more days already but got almost all about 'reference as member of class' so far)?
Take a look at §5.2.5 (Class member access in Postfix Expressions, from N3797 C++14 Draft):
Abbreviating postfix-expression.id-expression as E1.E2, E1 is called the object expression. The type and value category of E1.E2 are determined as follows. In the remainder of 5.2.5, cq represents either const or the absence of const and vq represents either volatile or the absence of volatile. cv represents an
arbitrary set of cv-qualifiers, as defined in 3.9.3.
If E2 is declared to have type “reference to T,” then E1.E2 is an lvalue; the type of E1.E2 is T. Otherwise,
one of the following rules applies.
If E2 is a non-static data member and the type of E1 is “cq1 vq1 X”, and the type of E2 is “cq2 vq2 T”, the expression designates the named member of the object designated by the first expression. If E1 is an lvalue, then E1.E2 is an lvalue; otherwise E1.E2 is an xvalue. Let the notation vq12 stand for the “union” of vq1 and vq2 ; that is, if vq1 or vq2 is volatile, then vq12 is volatile. Similarly, let the notation cq12 stand for the “union” of cq1 and cq2 ; that is, if cq1 or cq2 is const, then cq12 is const. If E2 is declared to be a mutable member, then the type of E1.E2 is “vq12 T”. If E2 is not declared to be a mutable member, then the type of E1.E2 is “cq12 vq12 T”.
The standard says nothing about E2 becoming a reference even if E1 is. To be explicit, if E1 is a reference type and E2 is not, E1.E2 is not a reference type.
Related
struct Test{
Test(int& v):rf(v){}
int& rf;
int obj;
};
int main(){
int i = 0;
Test t{i};
t.rf = 1;
t.obj = 1;
}
According to these rules for class member access:
If E2 is declared to have type “reference to T”, then E1.E2 is an lvalue; the type of E1.E2 is T. Otherwise, one of the following rules applies.
The clause only says what value category and type the expression are when E2 is of reference type. It's unlike these clauses that apply for the expression E1.E2, where E2 is not of reference type. which discuss what the entity denoted by such an expression implicitly. Such as:
If E2 is a static data member and the type of E2 is T, then E1.E2 is an lvalue; the expression designates the named member of the class.
If E2 is a non-static data member and the type of E1 is “cq1 vq1 X”, and the type of E2 is “cq2 vq2 T”, the expression designates the named member of the object designated by the first expression.
If it refers to a static member function and the type of E2 is “function of parameter-type-list returning T”, then E1.E2 is an lvalue; the expression designates the static member function.
Otherwise, if E1.E2 refers to a non-static member function and the type of E2 is “function of parameter-type-list cv ref-qualifier opt
returning T”, then E1.E2 is a prvalue. The expression designates a non-static member function.
The section [expr.ref] says all the situations that what the entity denoted by E1.E2 except that when E2 is of reference type.
basic.lval#2
The result of a glvalue is the entity denoted by the expression.
Such as t.obj, Because obj is a non-static non-reference data member(one of the following rules applies, the expression designates the named member of the object designated by the first expression), hence The entity denoted by the expression t.obj is the member obj of the object t, namely the subobject obj within t, So the result of such lvalue is such an entity.
However, For the example t.rf, there's no any quote in [expr.ref] points out what's the entity denoted by this expression? when applying assignment operation to such lvalue t.rf = 1;, t.rf as a glvalue, which should be determined which entity it denotes, then the right operand will modify the value of the entity. So, My question is:
what's the entity the expression t.rf denotes, which rule in the standard says that?
#include <iostream>
#include <type_traits>
struct A { double x; };
int main()
{
const A && a1 = A();
std::cout << std::is_same_v<decltype((a1.x)), const double&>;
std::cout << std::is_same_v<decltype((std::move(a1).x)), const double&&>;
std::cout << std::is_same_v<decltype((A().x)), double>;
}
Output:
111
Shouldn't decltype in the last example return double&& since according to value categories. A().x is an xvalue
xvalue
a.m, the member of object expression, where a is an rvalue and m is a
non-static data member of non-reference type;
...
Tested with gcc7.1;gcc5.2;clang3.8;gcc4.9;gcc4.8;gcc4.7 in the code snippet of en.cppreference.com/w/cpp/language/decltype
Going strictly by the standard, it appears you're right and it should be double &&. My chain of reasoning (all quotes from C++17 (n4659)):
8.2.5 Class member access [expr.ref]
1 A postfix expression followed by a dot . or an arrow ->, optionally followed by the keyword template (17.2),
and then followed by an id-expression, is a postfix expression. ...
2 For the first option (dot) the first expression shall be a glvalue having complete class type.
3 Abbreviating postfix-expression.id-expression as E1.E2, E1 is called the object expression. ... The type and value category of E1.E2 are determined as follows. In the remainder
of 8.2.5, cq represents either const or the absence of const and vq represents either volatile or the absence
of volatile. cv represents an arbitrary set of cv-qualifiers, as defined in 6.9.3.
...
(4.2) If E2 is a non-static data member and the type of E1 is “cq1 vq1 X”, and the type of E2 is “cq2 vq2 T”,
the expression designates the named member of the object designated by the first expression. If E1 is
an lvalue, then E1.E2 is an lvalue; otherwise E1.E2 is an xvalue. ...
So if the left-hand side operand of . is an xvalue, so is the result of the entire . expression.
8.2.3 Explicit type conversion (functional notation) [expr.type.conv]
1 A simple-type-specifier (10.1.7.2) or typename-specifier (17.6) followed by a parenthesized optional expression-list
or by a braced-init-list (the initializer) constructs a value of the specified type given the initializer. ...
2 ... the expression
is a prvalue of the specified type whose result object is direct-initialized (11.6) with the initializer.
So A() is a prvalue.
Finally:
8 Expressions [expr]
10 Whenever a prvalue expression appears as an operand of an operator that expects a glvalue for that operand,
the temporary materialization conversion (7.4) is applied to convert the expression to an xvalue.
Taken together, this means that A() is a prvalue (from 8.2.3/2). Since . requires its LHS operand to be a glvalue, the temporary materialisation conversion is appplied (per 8/10) and the result is an xvalue. So, from 8.2.5/(4.2), since E1 is an xvalue, so is E1.E2, which is A().x in your case.
As for decltype:
10.1.7.2 Simple type specifiers [dcl.type.simple]
4 For an expression e, the type denoted by decltype(e) is defined as follows:
...
(4.3) ... if e is an xvalue, decltype(e) is T&&, where T is the type of e;
Since in your case, (A().x) was determined to be an xvalue, its dectlype should be double &&.
According to [expr.ref]/(4.2) and the fact that A() is a prvalue, we conclude that A().a[0] is an xvalue. See highlighted sentence below.
If E2 is a non-static data member and the type of E1 is “cq1 vq1 X”,
and the type of E2 is “cq2 vq2 T”, the expression designates the named
member of the object designated by the first expression. If E1 is an
lvalue, then E1.E2 is an lvalue; otherwise E1.E2 is an xvalue. Let the
notation vq12 stand for the “union” of vq1 and vq2; that is, if vq1 or
vq2 is volatile, then vq12 is volatile. Similarly, let the notation
cq12 stand for the “union” of cq1 and cq2; that is, if cq1 or cq2 is
const, then cq12 is const. If E2 is declared to be a mutable member,
then the type of E1.E2 is “vq12 T”. If E2 is not declared to be a
mutable member, then the type of E1.E2 is “cq12 vq12 T.
Therefore the snippet below should compile. The code doesn't compile in GCC, neither in VS2017, but it compiles in clang.
#include<iostream>
struct A
{
int a[3];
A(): a{1, 2, 3} {}
};
int main()
{
int &&r = A().a[0];
std::cout << r << '\n';
}
However, the wording in [expr.sub]/1 indicates that a[0] is an lvalue, irrespective of the value category of A(), and that seems to be incorrect to me.
A postfix expression followed by an expression in square brackets is a
postfix expression. One of the expressions shall be a glvalue of type
“array of T” or a prvalue of type “pointer to T” and the other shall
be a prvalue of unscoped enumeration or integral type. The result is
of type “T”. The type “T” shall be a completely-defined object type.66
The expression E1[E2] is identical (by definition) to *((E1)+(E2)) [
Note: see 8.5.2 and 8.5.6 for details of * and + and 11.3.4 for
details of arrays. —end note ] , except that in the case of an array
operand, the result is an lvalue if that operand is an lvalue and an
xvalue otherwise. The expression E1 is sequenced before the expression
E2.
There is no contradiction, you're just parsing the expression wrongly. If I understand you correctly, you're saying that E1 is A() and E2 is a[0] which is not the case.
In fact, E2 is a. Because . and [] have the same precedence, the expression is parsed as (A().a)[0].
This means that according to [expr.ref]p4.2, A().a is an xvalue (of array type), and so that per [expr.sub]p1 (A().a)[0] is thus an xvalue.
Lets take two structs/classes
struct C1{
C1(){};
C1(C1&){std::cout<<"copy"<<std::endl;}
C1(C1&&){std::cout<<"move"<<std::endl;}};
struct C2{
C1 c;
C2(){};
C1 get1(){return c;}
C1 get2(){return std::move(c);}};
And than
C1 a1=C2().c;
C1 a2=C2().get1();
C1 a3=C2().get2();
the output is
move
copy
move
Question
We know that members of rvalues are rvalues themselves. This is why with a1, the move constructor is called. Why than, is the copy constructor called in the case of a2. We are returning an rvalue from a function.
To put it differently, std::move casts to an rvalue. But, as a member of an rvalue, c, is already an rvalue. Why than is there a difference between the behavior with a2 and a3?
Good question. The dull answer is that there's just no no rule in the C++ spec that says that returning a member from a dying object like this automatically moves it.
You may be interested in what's called rvalue reference to this. It let's you overload on && and & such that you can manually implement the behaviour you expected:
struct C2{
C1 c;
C2(){};
C1 get1() & { std::cout << "&" << std::endl; return c;}
C1 get1() && { std::cout << "&&" << std::endl; return std::move(c);}
C1 get2(){return std::move(c);}
};
Now
C1 a2=C2().get1();
prints
&&
move
Cool, but quite rare.
Related:
What is "rvalue reference for *this"?
Is a member of an rvalue structure an rvalue or lvalue?
We know that members of rvalues are rvalues themselves.
Yes this is true, as states [expr.ref]/4.2 (emphasis mine):
If E2 is a non-static data member and the type of E1 is “cq1 vq1 X”, and the type of E2 is “cq2 vq2 T”, the expression designates the named member of the object designated by the first expression. If E1 is an lvalue, then E1.E2 is an lvalue; otherwise E1.E2 is an xvalue. Let the notation vq12 stand for the “union” of vq1 and vq2; that is, if vq1 or vq2 is volatile, then vq12 is volatile. Similarly, let the notation cq12 stand for the “union” of cq1 and cq2; that is, if cq1 or cq2 is const, then cq12 is const. If E2 is declared to be a mutable member, then the type of E1.E2 is “vq12 T”. If E2 is not declared to be a mutable member, then the type of E1.E2 is “cq12 vq12 T”.
And also, from [expr.ref]/4.5:
If E2 is a member enumerator and the type of E2 is T, the expression E1.E2 is a prvalue. The type of E1.E2 is T.
So far so good. You can only get an lvalue if E1 is an lvalue itself, otherwise it's an xvalue or a prvalue.
But, as a member of an rvalue, c, is already an rvalue.
This is where your assumptions are wrong.
From [class.this]/1 (emphasis mine)
In the body of a non-static (9.3) member function, the keyword this is a prvalue expression whose value
is the address of the object for which the function is called. The type of this in a member function of
a class X is X*.
this is a prvalue of type X*, and dereferencing a pointer of type X yield an lvalue of type X.
Since accessing a member inside a member function is equivalent to (*this).m, then m is accessed through an lvalue of type X.
So your code is equivalent to:
C1 get1() { return (*this).c; }
// lvalue ----^ ^--- must be an lvalue too then.
Since this is always the same type, then even when using a function ref-qualifier the expression c inside a member function will always be an lvalue:
C1 get1() && { return (*this).c; }
// ^---- lvalue again, accessing through a pointer
I'm looking at the standard 5.16 paragraph 3, trying to understand what is going on. Consider the type M defined as
struct M {
M();
M(const M&);
M(M&&);
};
If I have a ternary expression pred ? E1 : E2, where the type of E1 is const M& and the type of E2 is M&& does 5.16 paragraph 3 bullet 1 apply?
— If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (Clause 4) to the
type “lvalue reference to T2”, subject to the constraint that in the conversion the reference must bind
directly (8.5.3) to an lvalue.
I think it doesn't, because to have an implicit conversion to const M&, which requires M to have the member function operator const M&().
However, I'm not sure, because it could be converted to const M implicitly, can the reference be implicitly added?
If it is implicitly convertible, does M&& bind directly to const M&?
I went through the procedure in 8.5.3, and I think that paragraph 5 bullet 2 is where this case falls, so it does bind directly, but I'm not certain.
— If the initializer expression [..] has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be
implicitly converted to an xvalue, class prvalue, or function lvalue of type “cv3 T3”, where
“cv1 T1” is reference-compatible with “cv3 T3”
You don't have expressions of type M&&, instead it would be adjusted to be an xvalue of type M.
So the question is: if you have an xvalue of type M, can it be implicitly converted to lvalue reference to const M? The answer is yes, since a const lvalue reference can be initialized with an rvalue. Such a reference binding is direct since it falls under the following case:
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”, ...
rather than the last case that involves construction of a temporary, which is the indirect binding case.
Therefore this use of the conditional operator will be well-formed. The xvalue of type M will be converted to an lvalue of type const M. Then the lvalue-to-rvalue conversion will be applied and the result will be a prvalue of type const M.