cv-qualifier propagation in structured binding - c++

As quoted in dcl.struct.bind,
Let cv denote the cv-qualifiers in the decl-specifier-seq.
Designating the non-static data members of E as m 0 , m 1 , m 2 , ... (in declaration order), each v i is the name of an lvalue that refers to the member m i of e and whose type is cv T i , where T i is the declared type of that member;
If I'm understanding correctly, the cv-qualifiers are propagated from the declartion of structured binding.
Say I have a simple struct,
struct Foo {
int x;
double y;
};
Consider the two scenarios,
const Foo f{1, 1.0};
auto& [x, y] = f;
// static_assert(std::is_same<decltype(x), int>::value); // Fails!
static_assert(std::is_same<decltype(x), const int>::value); // Succeeds
Live Demo.
Does the cv-qualifier of x come from the deduction auto?
The second one,
Foo f{1, 1.0};
const auto& [x, y] = f;
const auto& rf = f;
static_assert(std::is_same<decltype(x), const int>::value); // with const
static_assert(std::is_same<decltype(rf.x), int>::value); // without const
Live Demo. The result complies with the standard, which makes sense.
My second question is is there any reason to propagate the cv-qualifiers, isn't it a kind of inconsistent (to the initialization a reference with auto)?

decltype has a special rule when the member of a class is named directly as an unparenthesized member access expression. Instead of producing the result it would usually if the expression was treated as an expression, it will result in the declared type of the member.
So decltype(rf.x) gives int, because x is declared as int. You can force decltype to behave as it would for other expressions by putting extra parentheses (decltype((rf.x))), in which case it will give const int& since it is an lvalue expression and an access through a const reference.
Similarly there are special rules for decltype if a structured binding is named directly (without parentheses), which is why you don't get const int& for decltype(x).
However the rules for structured bindings take the type from the member access expression as an expression if the member is not a reference type, which is why const is propagated. At least that is the case since the post-C++20 resolution of CWG issue 2312 which intends to make the const propagation work correctly with mutable members.
Before the resolution the type of the structured binding was actually specified to just be the declared type of the member with the cv-qualifiers of the structured binding declaration added, as you are quoting in your question.
I might be missing some detail on what declared type refers to exactly, but it seems to me that this didn't actually specify x to have type const int& in your first snippet (and decltype hence also not const), although that seems to be how all compilers always handled that case and is also the only behavior that makes sense. Maybe it was another defect, silently or unintentionally fixed by CWG 2312.
So, practically speaking, both rf.x and x in your example are const int lvalue expressions when you use them as expressions. The only oddity here is in how decltype behaves.

The cvref-qualifiers don't always propagate. They apply only to the single hidden variable that stores a copy/reference to the initializer.
Ref-qualifiers don't affect the identifiers created by the binding, those always act as references to the said common hidden variable. But if that variable itself is a copy, this causes them to behave almost like they were true copies.
How cv-qualifiers propagate is affected by what you're binding:
For non-tuple-like classes and arrays, the behavior is defined in terms of . and [] respectively, which normally propagate const, except for mutable class members.
For tuple-like classes, it depends on how get<I>() is implemented for your type. For tuples and similar classes, it propagates const only if the element is not a reference.
E.g. for a tuple of non-const references, the binding will always produce non-const identifiers.
The inconsistency you're observing for decltype(x) vs decltype(rf.x) is caused by both of them being different special cases for decltype. Arguably the former makes more sense, but it's too late to change the latter.
If you add a second pair of parentheses, you'll get the same behavior for both.

Related

Choosing function overload with rvalue references [duplicate]

Given all three functions, this call is ambiguous.
int f( int );
int f( int && );
int f( int const & );
int q = f( 3 );
Removing f( int ) causes both Clang and GCC to prefer the rvalue reference over the lvalue reference. But instead removing either reference overload results in ambiguity with f( int ).
Overload resolution is usually done in terms of a strict partial ordering, but int seems to be equivalent to two things which are not equivalent to each other. What are the rules here? I seem to recall a defect report about this.
Is there any chance int && may be preferred over int in a future standard? The reference must bind to an initializer, whereas the object type is not so constrained. So overloading between T and T && could effectively mean "use the existing object if I've been given ownership, otherwise make a copy." (This is similar to pure pass-by-value, but saves the overhead of moving.) As these compilers currently work, this must be done by overloading T const & and T &&, and explicitly copying. But I'm not even sure even that is strictly standard.
What are the rules here?
As there is only one parameter, the rule is that one of the three viable parameter initializations of that parameter must be a better match than both the other two. When two initializations are compared, either one is better than the other, or neither is better (they are indistinguishable).
Without special rules about direct reference binding, all three initializations mentioned would be indistinguishable (in all three comparisons).
The special rules about direct reference binding make int&& better than const int&, but neither is better or worse than int. Therefore there is no best match:
S1 S2
int int&& indistinguishable
int const int& indistinguishable
int&& const int& S1 better
int&& is better than const int& because of 13.3.3.2:
S1 and S2 are reference bindings (8.5.3) and neither refers to an implicit object parameter of a non-static member function declared without a ref-qualifier, and S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference.
But this rule does not apply when one of the initializations is not a reference binding.
Is there any chance int && may be preferred over int in a future standard? The reference must bind to an initializer, whereas the object type is not so constrained. So overloading between T and T && could effectively mean "use the existing object if I've been given ownership, otherwise make a copy."
You propose to make a reference binding a better match than a non-reference binding. Why not post your idea to isocpp future proposals. SO is not the best for subjective discussion / opinion.

Is it allowed to use decltype in an initializer for the variable that is decltyped?

Triggered by this question, I was wondering if, this is allowed:
template <typename T>
T foo(){return T{};}
struct bar {};
int main()
{
bar a = foo<decltype(a)>();
}
Compilers I tried took it without complaints, but I am not sure if this is really legal or if I am missing any pitfalls (and it looks weird to use the type of a during its declaration).
For background: in the linked question OP wants to avoid auto and spelling out the type (here it is bar, SomeComplexTypeAndNotAuto in that question) twice at the same time, hence they use a (unused) parameter to deduce T. I didn't like misusing a parameter merely to deduce the type so my first idea was decltype.
It's kosher alright.
[basic.scope.pdecl]
1 The point of declaration for a name is immediately after its
complete declarator ([dcl.decl]) and before its initializer (if any),
except as noted below. [ Example:
unsigned char x = 12;
{ unsigned char x = x; }
Here, the initialization of the second x has undefined behavior,
because the initializer accesses the second x outside its lifetime
([basic.life]). — end example ]
So you can use a in its own initializer, since its declared at the point. The only question is how. decltype can be applied to an id-expression naming any variable in scope. And since the expression of decltype is an unevaluated operand, there is no UB. Only the type of the variable is examined, not its indeterminate value.
No accounting for taste though.

Scott Meyers on Rvalueness

I watched Scott Meyers's extremely informative video on Universal References, in which I learned most of what I know about Rvalue references, moving, and forwarding. At one point he was talking about rvalueness as opposed to the type of a variable, and he said something to the effect of "rvalueness is independent of type".
I understand that you can have a method like this:
void func(MyType&& rRef)
{
// Do domething with rRef...
}
and that here rRef is an lvalue because it can be identified, its address can be taken, etc., even though its type is MyType&&.
But an rvalue cannot be any type, can it? I mean, it can only be a MyType&&, right? In that sense I thought type is not entirely independent of rvalueness. Maybe I'm missing something.
Updated: My point can be made clearer like this. If in func() I call one of two overloaded functions defined as
void gunc(MyType&& rRef)
{
// ...
}
void gunc(MyType& lRef)
{
// ...
}
i.e. either by calling gunc(std::move(rRef)) or gunc(rRef), it seems that the type of the resulting expression between parenthesis is not independent of rvalueness.
The type of an expression does not have any traces of references. So if for a moment we assume that references could have reference type, then we would have the following
int a = 0;
int &ra = a;
int c = a + 42;
int d = ra + 42;
In the above, the expression a would have type int, and the expression ra would have type int&. I think that in nearly all the rules of the spec that relate expressions to type, for example rules that say "expression E must be of type X", we would have to add "... or reference to type X" (think about cast operators). So my educated guess is that this would be too much of a burden to be useful.
C++ has the following types
static type of an expression
Just called "type of the expression" (if not otherwise specified that the dynamic one is meant). This is a property of expressions that designate the type of expressions abstracted away of what the expression refers to at compile time. For example if a refers to an int& or int variable, or is a literal 0, all those expression have type int.
dynamic type of an lvalue expression
This is the type that the non-base-class object that an lvalue expression refers to has.
ofstream fs("/log");
ostream &os = fs;
In this, os has the static type ostream and the dynamic type ofstream.
type of an object or reference
This is the type that an object or reference actually has. An object always has a single type and its type never changes. But what object exists at what location is something only known at runtime, so generally, "type of an object" is a runtime thing too
ostream *os;
if(file)
os = new ofstream("/log");
else
os = new ostringstream;
The type of the object denoted by *os (and the dynamic type of the lvalue *os aswell) is known only at runtime
int *p = new int[rand() % 5 + 1];
Here, the type of the array that was created by operator new is only known at runtime too, and (thanksfully) does and can not escape to the static C++ type system. The infamous aliasing rule (that forbids reading objects from incompatible lvalues, roughly speaking) speaks of "dynamic type" of objects, presumably because it wants to highlight that runtime concerns are of interests. But strictly speaking, saying "dynamic type" of an object is weird, because an object doesn't have a "static type".
declared type of a variable or member
This is the type that you gave in a declaration. In relation to the type of an object or the type of an expression, this sometimes can be subtly different
struct A {
A() { }
int a;
};
const A *a = new const A;
volatile const A *va = a;
Here, the expression a->a has type const int, but the declared type of the member that a->a resolves to has type int (the member entity). The type of the object denoted by a->a has type const int, because we created a const A object with the new expression, and therefore all non-static data members are implicitly const subobjects. In va->a, the expression has type volatile const int a, and the declared type of the variable still has type int and the type of the object referred to still has type const int.
When you say "type of a" and you declared "a" as "int &a;" you therefor always have to say what you mean by "type of a". Do you mean the expression? Then "a" has type int. It can even become nastier. Imagine "int a[10];". Here the expression "a" has type int* or int[10] depending on whether you consider the array to pointer conversion to have taken place in your expression or not, when you ask for the "type of a". If you ask for the type of the variable referred to by "a", then the answer uniquely is int and int[10] respectively.
So what type can an rvalue be of? Rvalues are expressions.
int &&x = 0;
int y = std::move(x);
int z = x;
Here, we have rvalues 0, and std::move(x). Both rvalues have type int. The expression x appearing in the initializer for z is an lvalue, even though it refers to the same object that the rvalue std::move(x) refers to.
Your last point in your question about the overloaded function called with an rvalue or lvalue respectively is interesting. Just because rvalue references are written as int && does not mean that rvalues have type int. They are called rvalue references because you can initialize them with rvalues and the language prefers that initialization over initializing an lvalue reference with an rvalue.
Also, it may be useful to see expressions in name-form that are rvalues
enum A { X };
template<int Y> struct B { };
If you use X or Y, they are rvalues. But those cases are the only one that I can think of.
I think you are leaving off part of his quote:
A final point is worth bearing in mind: the lvalueness or rvalueness
of an expression is independent of its type.
He explains it here. His main point was this:
The type of an expression does not tell you whether it is an lvalue or
an rvalue.
And in his concluding remarks:
In a type declaration, “&&” indicates either an rvalue reference or a
universal reference – a reference that may resolve to either an lvalue
reference or an rvalue reference. Universal references always have the
form T&& for some deduced type T.
Reference collapsing is the mechanism that leads to universal
references (which are really just rvalue references in situations
where reference-collapsing takes place) sometimes resolving to lvalue
references and sometimes to rvalue references. It occurs in specified
contexts where references to references may arise during compilation.
Those contexts are template type deduction, auto type deduction,
typedef formation and use, and decltype expressions.
The type T is used here to mean any type. It could be int&&, or double&&, or Widget&& - it doesn't matter.
First, let's limit the discussion to plain rvalue references and leave universal references aside. We're not talking about template <typename T> ... T &&var ...
As far as regular Type &&var = somevalue; case of rvalue reference, I think the meaning of it is this:
Whatever is bound to this reference was "disposable" when it was bound to the reference. It was going out of scope. If you modified it at the moment you bound it, no one would ever know. There were no other references to it at the time it was bound.
This allows us to take some liberties with rvalue references that we cannot take with other kinds of variables. The first use that comes to mind is to steal its contents with swap().

Overload resolution between object, rvalue reference, const reference

Given all three functions, this call is ambiguous.
int f( int );
int f( int && );
int f( int const & );
int q = f( 3 );
Removing f( int ) causes both Clang and GCC to prefer the rvalue reference over the lvalue reference. But instead removing either reference overload results in ambiguity with f( int ).
Overload resolution is usually done in terms of a strict partial ordering, but int seems to be equivalent to two things which are not equivalent to each other. What are the rules here? I seem to recall a defect report about this.
Is there any chance int && may be preferred over int in a future standard? The reference must bind to an initializer, whereas the object type is not so constrained. So overloading between T and T && could effectively mean "use the existing object if I've been given ownership, otherwise make a copy." (This is similar to pure pass-by-value, but saves the overhead of moving.) As these compilers currently work, this must be done by overloading T const & and T &&, and explicitly copying. But I'm not even sure even that is strictly standard.
What are the rules here?
As there is only one parameter, the rule is that one of the three viable parameter initializations of that parameter must be a better match than both the other two. When two initializations are compared, either one is better than the other, or neither is better (they are indistinguishable).
Without special rules about direct reference binding, all three initializations mentioned would be indistinguishable (in all three comparisons).
The special rules about direct reference binding make int&& better than const int&, but neither is better or worse than int. Therefore there is no best match:
S1 S2
int int&& indistinguishable
int const int& indistinguishable
int&& const int& S1 better
int&& is better than const int& because of 13.3.3.2:
S1 and S2 are reference bindings (8.5.3) and neither refers to an implicit object parameter of a non-static member function declared without a ref-qualifier, and S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference.
But this rule does not apply when one of the initializations is not a reference binding.
Is there any chance int && may be preferred over int in a future standard? The reference must bind to an initializer, whereas the object type is not so constrained. So overloading between T and T && could effectively mean "use the existing object if I've been given ownership, otherwise make a copy."
You propose to make a reference binding a better match than a non-reference binding. Why not post your idea to isocpp future proposals. SO is not the best for subjective discussion / opinion.

Is a function return-value constant by default (an rvalue)?

I'm learning about rvalue references, and the tutorial told me this:
X foo();
X x;
x = foo();
Rather obviously, it would be ok, and much more efficient, to swap resource pointers (handles) between x and the
temporary, and then let the temporary's destructor destruct x's original resource.
In other words, in the special case where the right hand side of the
assignment is an rvalue, we want the copy assignment operator to act
like this.
So, does this mean that return values from functions are always constant by default, and thereby an rvalue? If yes: Are they always constant, or are there exceptions too?
Rvalue-ness and constant-ness are not synonyms, but rather a bit orthogonal. With the following definitions:
struct X {};
const X x;
const X f();
int X();
We can categorize the following expressions:
x; // constant lvalue
f(); // constant rvalue
g(); // non-constant rvalue
As of your particular question: no, not all rvalue expressions are constant.
So, does this mean that return values from functions are always constant by default, and thereby an rvalue? If yes: Are they always constant, or are there exceptions too?
No. They are rvalues iff they don't return a reference type (cv T& or cv T&&). They are constant iff their return type is const-qualified.
That means a return value from a function X foo() is an rvalue (prvalue, if you want new standardese), and not a constant. Moreover, in an expression like x = foo(), we usually don't care if the temporary changes during the assignment which is pretty much the idea behind move-semantics.
§5.2.2/10 (in N3225) states:
A function call is an lvalue if the result type is an lvalue reference type or an rvalue reference to function type, an xvalue if the result type is an rvalue reference to object type, and a prvalue otherwise.
You might be confusing types, objects and expressions. Only expressions have a notion of lvalue/rvalueness. The expression foo(); is an rvalue of type X. As such, the statement x = foo(); will invoke -- if possible -- the member function X::operator=(X &&) of x. Failing that, it will bind to the standard X::operator=(X const &), since rvalues bind to const-references.
Note that it is possible in theory to have constant rvalues, for example if you had a function declared as X const bar();. Then bar() would not bind to X&&, but only to X const && (as well as to X const &). There is no use for this in practice, though.
See this previous question, which tells us that neither are rvalue expressions neither implicitly of a const type, nor are the objects they represent made inherently immutable.
However, it is undefined (or forbidden — I forget which) in some cases to modify an object through an rvalue. This does seem to yield a sort of conditional inherent immutability to objects accessed through an rvalue, and the result of evaluating a function call is often — though not always! — an rvalue expression.