Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stmt.return] Fix the operand's conversions description #4107

Closed
wants to merge 1 commit into from

Conversation

languagelawyer
Copy link
Contributor

@languagelawyer languagelawyer commented Aug 1, 2020

This is a follow up on #3124/#3587 @sdkrystian @jensmaurer .

This is by no means complete, lotsa wording after the changes and till the end of the subclause seems very broken, namely:

The destructor for the returned object
is potentially invoked~(\ref{class.dtor}, \ref{except.ctor}).
\begin{example}
\begin{codeblock}
class A {
  ~A() {}
};
A f() { return A(); }   // error: destructor of \tcode{A} is private (even though it is never invoked)
\end{codeblock}
\end{example}

There is no "returned object" in A f() { return A(); }. The normative part and the Example should be reworded.

\pnum
The copy-initialization of the result of the call is sequenced before the...

We don't have a copy-initialization of the result, so this should also be reworded.

@languagelawyer languagelawyer marked this pull request as draft August 1, 2020 21:54
@languagelawyer languagelawyer changed the title [stmt.return] Fix operand conversions description [stmt.return] Fix the operand's conversions description Aug 1, 2020
@languagelawyer languagelawyer force-pushed the stmt.return branch 2 times, most recently from a5d8063 to 4787329 Compare August 1, 2020 22:30
@jensmaurer
Copy link
Member

Why don't we have copy-initialization for the return value?

@languagelawyer
Copy link
Contributor Author

languagelawyer commented Aug 1, 2020

Why don't we have copy-initialization for the return value?

Because we don't initialize values in C++. If you mean why don't we initialize the result, well, because the result is either a value, which I've already covered, or an object, which is already initialized somewhere, or function, which doesn't need initialization. For example:

int& f(int& ref) { return ref; #1 }

int main()
{
    int i = 0; #2
    f(i);
}

What does the return statement in #1 initialize? The result of its operand, which is i? It has already been initialized in #2.

@jensmaurer
Copy link
Member

We still call "int& i = x" copy-initialization, I believe. I'm not seeing a material difference to what we do for the "return" case (except for trying rvalues before lvalues in certain cases).

@languagelawyer
Copy link
Contributor Author

Basically what I'm trying to fix is described in this comment. After #3587, [expr.call] says:

The result of a function call is the result of the possibly-converted operand of the return statement...

With [stmt.return] saying:

the return statement initializes the glvalue result or prvalue result object of the (explicit or implicit) function call

we have a sort of circularity, as described in that comment: the return statement initializes the result of its operand from the result of its operand.

With my fix, [stmt.return] only determines the sequence of conversion. [expr.call] relies on it to say what is the result of a function call.

We still call "int& i = x" copy-initialization, I believe. I'm not seeing a material difference to what we do for the "return" case

I believe, more correct would be to say that in C++ we don't initialize objects, but variables and non-static data members. And in int& f(int& ref) { return ref; }, there is none of them. But this is a bit of a stretch.

BTW, you didn't answer what is initialized by the return statement in my code.

@jensmaurer
Copy link
Member

jensmaurer commented Aug 1, 2020

I think we initialize a return value (in the broadest sense; here, it's a reference) that is then discarded. (If we were returning an object, we'd call the destructor on it when we discard it.)

And we do initialize objects; there's a fresh initialization of the fresh object designated by the variable i for each recursive entry into f:

void f()
{
  int i = 0;
  f();  // pretend we terminate the recursion at some point
}

@languagelawyer
Copy link
Contributor Author

languagelawyer commented Aug 1, 2020

I think we initialize a return value (in the broadest sense; here, it's a reference)

A glvalue is an expression which denotes an object or function. The result of a glvalue is the entity object or function denoted by the expression. If we say «the return statement initializes the glvalue result», we can't initialize a reference, because there is no such kind of glvalue result.

@jensmaurer
Copy link
Member

The glvalue refers to i (at #2).

@languagelawyer
Copy link
Contributor Author

The glvalue refers to i

I don't understand what do you want to say here. The glvalue result (of f(i) or ref in the return statement) is i and the return statement initializes it again (I mean, after this object has been initialized in int i = 0;)?

@jensmaurer
Copy link
Member

No. The call f(i) results in a glvalue (which is discarded) that refers to the object designated by "i".

@languagelawyer
Copy link
Contributor Author

The call f(i) results in a glvalue (which is discarded) that refers to the object designated by "i".

Ok, IIUC we agree that the result of f(i) is the object designated by i. Now, [stmt.return] says: «the return statement initializes the glvalue result ... of the (explicit or implicit) function call by copy-initialization from the operand». So, the object designated by i is copy-initialized from the operand ref, which denotes the same object, the object designated by i. Can we initialize an object twice? 🤔

@jensmaurer
Copy link
Member

The result of f(i) is a glvalue that refers to i.
The copy-initialization is of an anonymous reference that turns into the glvalue result of the function call expression. We definitely don't initialize i upon return, and I'm not seeing the words as saying such. (I do agree there is room for improvement, but avoiding the term "copy-initialization" here might not be the best way forward.)

@jensmaurer jensmaurer added the decision-required A decision of the editorial group (or the Project Editor) is required. label Aug 1, 2020
@languagelawyer
Copy link
Contributor Author

languagelawyer commented Aug 2, 2020

The copy-initialization is of an anonymous reference

  1. As I've already said, there can't be such glvalue result as "a[n anonymous] reference".
  2. Where does "an anonymous reference" come from, after all? Which wording? The (IMO, abominable) wording «A return statement ... initializes the object or reference to be returned from the function» was removed from [stmt.return] by P0135R1. I think it is better not to resurrect that dead wording.

that turns into the glvalue result of the function call expression

[expr.call] says: «The result of a function call is the result of the possibly-converted operand of the return statement». There is no need in anonymous references.

avoiding the term "copy-initialization" here might not be the best way forward

I'm not avoiding it:

... if the parenthesized operand was used to copy-initialize a hypothetical variable ...

@jensmaurer
Copy link
Member

You're removing "return statement" from the list of copy-initialization contexts, it seems.

@languagelawyer
Copy link
Contributor Author

languagelawyer commented Aug 2, 2020

Because it doesn't really perform a copy-initialization. It uses imaginary variable copy-initialization just to determine the sequence of conversions to be applied to the operand. Maybe I should reword [stmt.return] to say that it initializes an imaginary variable by the initializer of the form = ( the operand ), then it will be 100% clear that "function return" doesn't need to be listed, because the initialization of such a form is already covered?

@jensmaurer
Copy link
Member

Saying that a hypothetical variable is copy-initialized sounds reasonable; I'm not sure we need to spell out the explicit syntax. However, if we say that, "return" should certainly stay in the list for copy-initialization in [dcl.init].

Before we dig any deeper, I'm increasingly getting the impression we're leaving "editorial issue" grounds here.

@languagelawyer
Copy link
Contributor Author

Before we dig any deeper, I'm increasingly getting the impression we're leaving "editorial issue" grounds here.

I understand, the change of the «the \tcode{return} statement initializes the ... prvalue result object» wording could raise doubts if programs have the same meaning under the new wording.

@languagelawyer
Copy link
Contributor Author

I'm not sure we need to spell out the explicit syntax.

BTW, do we need to parenthesize the operand?

@languagelawyer
Copy link
Contributor Author

BTW, do we need to parenthesize the operand?

We can't even parenthesize it, because if the operand is braced-init-list, the parenthesized operand can't be used as a variable initializer :(

@languagelawyer
Copy link
Contributor Author

So, after thinking for a little bit more: both [stmt.return] and [expr.call] need fix. More complex than which I've proposed here.
For functions with a non-reference return type, the current wording in [stmt.return]:

the return statement initializes the ... prvalue result object of the (explicit or implicit) function call by copy-initialization from the operand.

looks OK, but the current [expr.call] wording:

The result of a function call is the result of the possibly-converted operand of the return statement

looks defective. The operand can be a braced-init-list which is not a (prvalue) expression, so it doesn't have a result.

Functions with reference return types will have to be handled differently, probably in a way similar to how it was proposed at the end of #3124 (comment)

@xmh0511
Copy link
Contributor

xmh0511 commented Sep 2, 2020

So, after thinking for a little bit more: both [stmt.return] and [expr.call] need fix. More complex than which I've proposed here.
For functions with a non-reference return type, the current wording in [stmt.return]:

the return statement initializes the ... prvalue result object of the (explicit or implicit) function call by copy-initialization from the operand.

looks OK, but the current [expr.call] wording:

The result of a function call is the result of the possibly-converted operand of the return statement

looks defective. The operand can be a braced-init-list which is not a (prvalue) expression, so it doesn't have a result.

Functions with reference return types will have to be handled differently, probably in a way similar to how it was proposed at the end of #3124 (comment)

Agree! I think the wording "glvalue result" is so unclear in [stmt.return] p2 sentence 4, what actually the wording intents to refer to?

Maybe the proposal in that link should be changed to :

If a function call is a prvalue, the prvalue result object is copy-initialized with the possibly-converted operand of the return statement which transferred control out of the called function. Otherwise, it's a gvalue with the type to which the return type referenced , such a glvalue refers to the possibly-converted operand of the return statement which transferred control out of the called function.

I think It will be more better. My reason is , It will also cover this case:

int& f(int& ref) { return ref; #1 }

int main()
{
    int i = 0; #2
    int v = f(i); // ok , `f(i)` is a glvalue of type int, which refers to `i`. and v is copy-initialized by the result from after applying l-to-r conversion to `i`.   
}

That is said, the entity which will be initialized by f(i) is not necessary of reference type.

@jwakely
Copy link
Member

jwakely commented Sep 2, 2020

The suggested changes do not seem like an improvement to me. They do not make anything more clear. Quite the opposite.

[stmt.return] defines what happens. [expr.call] is just referring to that definition not redefining it or contradicting it. Arguably the language in [expr.call] is a bit informal, but I don't see any circularity.

I read the "is the result of the possibly-converted operand" in [expr.call] to mean the same thing as [stmt.return], namely that the result is copy-initialized from the operand, which might require a conversion.

@xmh0511
Copy link
Contributor

xmh0511 commented Sep 2, 2020

The suggested changes do not seem like an improvement to me. They do not make anything more clear. Quite the opposite.

[stmt.return] defines what happens. [expr.call] is just referring to that definition not redefining it or contradicting it. Arguably the language in [expr.call] is a bit informal, but I don't see any circularity.

I read the "is the result of the possibly-converted operand" in [expr.call] to mean the same thing as [stmt.return], namely that the result is copy-initialized from the operand, which might require a conversion.

However, I think the wording "glvalue result" in [stmt.return] is unclear. where is the definition for such a wording in the standard document? So for example,

int& func(int& ref){
  return ref;
}
int main(){
  int a = 0;
  int& v = func(a);
}

The function call expression, namely func(a) is used to initialize a reference v, However what's the "glvalue result" in this declaration? Is the following rule relevant to this wording?

The result of a glvalue is the entity denoted by the expression.

Consider the below rule which is said in [stmt.return] section

the return statement initializes the glvalue result or prvalue result object of the (explicit or implicit) function call by copy-initialization from the operand.

IIUC, for my example, presumably, what the actually intent of this sentence is saying that, the reference v is copy-initialized by operand of return statement as if it is initialized by int& v = ref;

But I have to say that in the declaration int& v = func(a); where v is a declarator-id, how could it be an expression? much less to say it's a glvalue.

@jwakely
Copy link
Member

jwakely commented Sep 2, 2020

The result of the function is just the expression func(a) not the statement that also declares v.

@languagelawyer
Copy link
Contributor Author

languagelawyer commented Sep 2, 2020

The suggested changes do not seem like an improvement to me. They do not make anything more clear. Quite the opposite.

The aim to make "anything more clear" of the suggested changes is not. The aim is to correctly formalize the guaranteed copy elision. Formalized wording often looks less "clear" than colloquial description. Ofc everyone "knows" that

T f() { return e; }
T v = f();

is "equivalent" to T v = e; (except that this is not 100% syntactic substitution, because e can be a comma-separated list of expressions, which is not allowed in the initializer grammar), without even looking into [expr.call] or [stmt.return].

I read the "is the result of the possibly-converted operand" in [expr.call] to mean the same thing as [stmt.return], namely that the result is copy-initialized from the operand, which might require a conversion.

As it was discussed above, in some cases this means that the [object, denoted by the] operand is copy-initialized from itself, when it has already been initialized somewhere else. This is nonsense.
Also, as I've already mentioned, the operand can be an initializer-list, like return {};, which is not "converted" and which is not an expression, so it is unclear what is the result of this operand.

I think the wording "glvalue result" in [stmt.return] is unclear. where is the definition for such a wording in the standard document?

https://timsong-cpp.github.io/cppwp/n4659/basic.lval#def:result,glvalue

@xmh0511
Copy link
Contributor

xmh0511 commented Sep 2, 2020

The suggested changes do not seem like an improvement to me. They do not make anything more clear. Quite the opposite.

Aim to make "anything more clear" of the suggested changes is not. The aim is to correctly formalize the guaranteed copy elision. Formalized wording often looks less "clear" than colloquial description. Ofc everyone "knows" that

T f() { return e; }
T v = f();

is "equivalent" to T v = e; (except that this is not 100% syntactic substitution, because e can be a comma-separated list of expressions, which is not allowed in the initializer grammar), without even looking into [expr.call] or [stmt.return].

I read the "is the result of the possibly-converted operand" in [expr.call] to mean the same thing as [stmt.return], namely that the result is copy-initialized from the operand, which might require a conversion.

As it was discussed above, in some cases this means that the [object, denoted by the] operand is copy-initialized from itself, when it has already been initialized somewhere else. This is nonsense.
Also, as I've already mentioned, the operand can be an initializer-list, like return {};, which is not "converted" and which is not an expression, so it is unclear what is the result of this operand.

I think the wording "glvalue result" in [stmt.return] is unclear. where is the definition for such a wording in the standard document?

https://timsong-cpp.github.io/cppwp/n4659/basic.lval#def:result,glvalue

Yes, that's the relevant quote. However, consider such a statement int& v = func(a);, which is the "glvalue result" in this declaration? Is it the expression func(a)?

@languagelawyer
Copy link
Contributor Author

The result of the function is just the expression func(a)

The result of a glvalue can be an object or function. Not an expression.

However, consider such a statement int& v = func(a);, which is the "glvalue result" in this declaration?

The result is [the object denoted by] a.

@xmh0511
Copy link
Contributor

xmh0511 commented Sep 5, 2020

The result of the function is just the expression func(a)

The result of a glvalue can be an object or function. Not an expression.

However, consider such a statement int& v = func(a);, which is the "glvalue result" in this declaration?

The result is [the object denoted by] a.

So, that is said , the "glvalue result" which is a is copy-initialized from the operand of the return statement, that is, it initializes which is itself. It's so weird.

@jensmaurer
Copy link
Member

Editorial meeting: "copy-initialization" is not good, talk about "full-expression is sequenced before".
Also, "returned object" is not good.

@languagelawyer
Copy link
Contributor Author

Also, "returned object" is not good.

#4112

@jensmaurer jensmaurer removed the decision-required A decision of the editorial group (or the Project Editor) is required. label Dec 4, 2020
@tkoeppe
Copy link
Contributor

tkoeppe commented Dec 14, 2020

@jensmaurer: To be clear, the present patch does not (yet) reflect the editorial consensus, right?

@jensmaurer
Copy link
Member

@jensmaurer: To be clear, the present patch does not (yet) reflect the editorial consensus, right?

Right.

@xmh0511
Copy link
Contributor

xmh0511 commented May 7, 2021

It appears to me that, the following wording would more consistent with the behavior of the major implementations.

  • the return statement initializes the prvalue result object of the (explicit or implicit) function call by copy-initialization from the operand.
  • Otherwise, if the function call is a glvalue, the return statement is equivalent to the following hypothetical declaration

T t = e; where e is the original operand of the return statement and T is the return type of the function.

The variable t is used as the result of the function call.

After this rewording, stmt.return#5 can still be consistent in either case. The t is so-called anonymous reference mentioned by @jensmaurer. Please take a look at this proposal @jensmaurer @languagelawyer

@languagelawyer
Copy link
Contributor Author

languagelawyer commented May 7, 2021

T t = e; where e is the original operand of the return statement

return allows comma-separated operand (e1, e2, ..., en), but initializer grammar doesn't.

The t is so-called anonymous reference

I think I don't want to see anonymous references back.

@xmh0511
Copy link
Contributor

xmh0511 commented May 7, 2021

T t = e; where e is the original operand of the return statement

return allows comma-separated operand (e1, e2, ..., en), but initializer grammar doesn't.

Why would be that? For declaration T t = (e1,e2,e3); where = (e1,e2,e3,...,en) is the initializer, which obeys the grammar initializer = initializer-clause . (e1,e2,e3,...,en) is the original operand of the return statement.

The t is so-called anonymous reference

I think I don't want to see anonymous references back.

I just say that t is the anonymous reference that jensmaurer said, it's not normative wording. I mean such wording can be used as the source for the informal wording anonymous reference

@languagelawyer
Copy link
Contributor Author

Why would be that? ... (e1,e2,e3,...,en) is the original operand of the return statement.

I meant return e1, e2, e3;, not return (e1, e2, e3);.

And the main difficulty here is how to describe function call prvalue result in case return's operand is a braced list. «The result of a function call is the result of the possibly-converted operand of the return statement» can't work since braced list is not an expression and thus doesn't have result.

@xmh0511
Copy link
Contributor

xmh0511 commented May 7, 2021

Why would be that? ... (e1,e2,e3,...,en) is the original operand of the return statement.

I meant return e1, e2, e3;, not return (e1, e2, e3);.

And the main difficulty here is how to describe function call prvalue result in case return's operand is a braced list. «The result of a function call is the result of the possibly-converted operand of the return statement» can't work since braced list is not an expression and thus doesn't have result.

I think it is not necessary to change the wording about copy-initialization for prvalue result object, it works well, it remains

the return statement initializes the prvalue result object of the (explicit or implicit) function call by copy-initialization from the operand.

It's sufficient well. no matter what the operand is(expression or braced-init-list). I think that we just pay attention to the wording for the case of the glvalue result.

I think we just amend the wording that I said by adding the special case return comma-expression.

@xmh0511
Copy link
Contributor

xmh0511 commented May 7, 2021

@languagelawyer Since you mention the special case return comma-expression, I also think that the same issue also exists in dcl.type.auto.deduct#2.1. fun(e1,e2) has the different syntactic meaning with fun((e1,e2)), which should be considered defect.

@languagelawyer
Copy link
Contributor Author

@xmh0511 #4315

@xmh0511
Copy link
Contributor

xmh0511 commented May 7, 2021

@xmh0511 #4315

Well. It's good wording. I think we can use the same manner for the special case return comma-expression in the wording for describing the return statement for the glvalue result case. So, change the wording to

Otherwise, if the function call is a glvalue, the return statement is equivalent to the following hypothetical declaration

T t = e;
if the operand is an expression X that is not an assignment-expression(i.e, comma expression), e is (X), otherwise e is X. T is the return type of the function call.

The variable t is used as the result of the function call.

@tkoeppe
Copy link
Contributor

tkoeppe commented Aug 22, 2022

Please reopen if you would like to continue with this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs rebase The pull request needs a git rebase to resolve merge conflicts.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants