"full-expression" versus "full expression" - c++

I was looking at this post in GitHub, but I couldn't understand what the OP meant by this:
"full expression" suggest that it is a kind of expression, but sometimes it is not.
My interpretation is that a "full-expression" (term used in the Standard) may not be an expression. [intro.execution]/5 gives the definition for full-expression, as follows:
A full-expression is
(5.1) — an unevaluated operand (8.2),
(5.2)
— a constant-expression (8.6),
(5.3) — an init-declarator (Clause
11) or a mem-initializer (15.6.2), including the constituent
expressions of the initializer,
(5.4) — an invocation of a
destructor generated at the end of the lifetime of an object other
than a temporary object (15.2), or
(5.5) — an expression that is not
a subexpression of another expression and that is not otherwise part
of a full-expression.
If my interpretation is correct I would like to know which of the bullet points above yields a full-expression that is not an expression. Otherwise, i.e., if I'm wrong, what did the OP mean by his comment?

The formal list of expressions can be found in [gram.expr]. It is quite a bit of text so I am not going to include it here but using it we can see that an init-declarator and a mem-initializer are not expressions according to the grammar. That means even though an init-declarator and a mem-initializer are considered full expressions, grammatically they are not expressions.

Related

C++ integral constant expression definition

In the current C++ standard there is the following paragraph (expr.const#5) (emphasis mine):
An integral constant expression is an expression of integral or unscoped enumeration type, implicitly converted to a prvalue, where the converted expression is a core constant expression. [ Note: Such expressions may be used as bit-field lengths, as enumerator initializers if the underlying type is not fixed ([dcl.enum]), and as alignments. — end note ]
I have two questions regarding this definition:
Does the phrase "implicitly converted to a prvalue" mean that for an expression to be considered an "integral constant expression" it must appear in a context that forces it to be implicitly converted to a prvalue?
What does "the converted expression" refer to? I know that this question is addressed in Clarification of converted constant expression definition. The answer given there is that "the converted expression" is t, after the following initialization: T t = expr;. However, I do not see how evaluating that expression (t) would match any of the rules given in [expr.const#4] (paragraph describing required conditions for an expression to be considered a core constant expression) which would make it unqualified to be a core constant expression.
Thank you.
The statement that an integral constant expression is implicitly converted to a prvalue means that the lvalue-to-rvalue conversion is applied to any expression used as an integral constant expression. In the one case where an expression might be an integral constant expression—initializing a non-local object of const-qualified integer type that might be usable in constant expressions—the initializer is a prvalue anyway, so no change of interpretation can occur.
Beyond that, both of your questions have the same answer: whatever conversions are necessary to bring the expression (as written) to a prvalue integral type must also be allowed in a core constant expression (see, for example, /4.7 just before your citation and /6 just after it). The “converted expression” comprises the conversion in the T t=e; interpretation, not just the id-expression t (which would, for instance, always be an lvalue).
I looked at clang's source code, specifically, at the function "CheckConvertedConstantExpression" inside "SemaOverload.cpp". The operations performed there are as follows:
find the required implicit conversion sequence
check to see if only conversions listed in http://eel.is/c++draft/expr.const#7 are used
perform the implicit conversion (at this step I believe a new expression is created, e.g. if the original expression is f() which is of class type A with a user-defined conversion function to int, and the context requires an int, then the new expression should be f().operator int())
check if any narrowing conversions are required
evaluate the expression generated at step 3 (which implicitly checks if it is a constant expression)
So I believe that, as stated in #Davis Herring's answer, the term "converted expression" means a new expression whose evaluation comprises both the evaluation of the original expression, as written in the program, and the evaluation of any required conversion.

C++ full-expression standard wording

In paragraph 12 of [intro.execution] in the current (C++ 17) C++ standard draft there is written:
A full-expression is:
[...]
an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.
If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition. [...]
The "use of the language construct" wording refers to the fact that the construct itself is to be considered an expression or the implicit calls that the construct "uses" are to be considered as expressions?
I am asking this because in the same paragraph there is this code sample:
S s1(1); // full-expression is call of S​::​S(int)
The comment would indicate that the second interpretation is correct.
However, the paragraph explicitly says that an init-declarator is a full-expression, which would indicate that the comment is wrong.
In the past (I believe even before C++03), this paragraph looked like this (taken from this defect report):
A full-expression is an expression that is not a subexpression of another expression. If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition.
[Note: certain contexts in C++ cause the evaluation of a full-expression that results from a syntactic construct other than expression (5.19 [expr.comma]). For example, in 8.6 [dcl.init] one syntax for initializer is
( expression-list )
but the resulting construct is a function call upon a constructor function with expression-list as an argument list; such a function call is a full-expression. For example, in 8.6 [dcl.init], another syntax for initializer is
= initializer-clause
but again the resulting construct might be a function call upon a constructor function with one assignment-expression as an argument; again, the function call is a full-expression. ]
This is another reason for believing that the second interpretation is the one intended.
I know that it makes no difference to the understanding of the language, but I just want to know what was the intention of the person who initially wrote that paragraph.

How are unqualified-ids other than identifier categorized in terms of lvalue and rvalue?

It seems that the standard does not explicitly talk about the expression categories of some unqualified-ids. On the other hand, an identifier, which is one of unqualified-ids, is categorized as follows:
The result is an lvalue if the entity is a function, variable, or data member and a prvalue otherwise.
Then, what about other unqualified-ids—operator-function-id, conversion-function-id, literal-operator-id, ~class-name, ~decltype-specifier, template-id?
This has been acknowledged as Core Working Group issue 536 which has been submitted almost 10 years ago. So probably it hasn't been deemed important enough to be fixed.
More importantly, some kinds of id-expressions are not described by
5.1.1 [expr.prim.general]. The structure of this section is that the result, type, and lvalue-ness are specified for each of the cases it
covers [...]
This treatment leaves unspecified all the non-identifier
unqualified-ids (operator-function-id, conversion-function-id, and
template-id) [...]
-- CWG 536
From the behaviour of various compilers, we can assume the intention is that all of those expressions follow the rule for identifiers:
The type of the expression is the type of the identifier. The result is the entity denoted by the identifier. The result is an lvalue if the entity is a function, variable, or data member and a prvalue otherwise.
-- post-N4296 [expr.prim.general]p13
By the way: all of those expressions but template-ids can only refer to functions. Template-ids can refer to variables as well. Both template-ids and ordinary identifiers can refer to classes and enums, which confused me slightly (can there be expressions such as int? are they prvalues?)

Why are lambda expressions not allowed in an unevaluated operands but allowed in the unevaluated portions of constant expressions?

If we look at the draft C++ standard section 5.1.2 Lambda expressions paragraph 2 says (emphasis mine going forward):
The evaluation of a lambda-expression results in a prvalue temporary (12.2). This temporary is called the closure object. A lambda-expression shall not appear in an unevaluated operand (Clause 5). [ Note: A closure object behaves like a function object (20.8).—end note ]
and section 5.19 Constant expressions paragraph 2 says:
A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression (3.2), but subexpressions of logical AND (5.14), logical OR (5.15), and conditional (5.16) operations that are not evaluated are not considered [...]
and has the following bullet:
— a lambda-expression (5.1.2);
So why are lambdas expressions not allowed in an unevaluated operand but are allowed in the unevaluated portions of constant expressions?
I can see how for unevaluated operands the type information in several cases(decltype or typeid) is not very useful since each lambda has a unique type. Although why we would want to allow them in the unevaluated context of a constant expression is not clear, perhaps to allow for SFINAE?
The core reason for the unevaluated operands exclusion is covered in C++ Standard Core Language Defect Reports and Accepted Issues #1607. Lambdas in template parameters which seeks to clarify this restriction and states the intention of the restriction in section 5.1.2 was to:
[...] avert the need to deal with them in function template signatures [...]
As the issue documents the current wording actually has a hole since constant expressions allows them in an unevaluated context. But it does not outright state the rationale for this restriction. The desire to avoid name mangling stands out and you can infer that avoiding extending SFINAE was also desired since the proposed resolution seeks to tighten the restriction even though several viable alternatives would have allowed SFINAE. The modified version of 5.1.2 paragraph 2 as follows:
A lambda-expression shall not appear in an unevaluated operand (Clause 5 [expr]), in a template-argument, in an alias-declaration, in a typedef declaration, or in the declaration of a function or function template outside its function body and default arguments [Note: The intention is to prevent lambdas from appearing in a signature —end note]. [Note: A closure object behaves like a function object (20.10 [function.objects]). —end note]
This proposal was accepted and is in N3936(see this answer for a link)
For a more explicit discussion of the rationale to avoid having lambdas as an unevaluated operand. The discussion titled Rationale for lambda-expressions not being allowed in unevaluated contexts on comp.lang.cpp.moderated Daniel Krügler lays out three reasons:
The extreme extension of possible SFINAE cases :
[...]The reason why they became excluded was due to exactly this extreme extension of sfinae cases (you were opening a Pandora box for the compiler)[...]
In many cases it is just useless since each lambda has a unique type, the hypothetical example given:
template<typename T, typename U>
void g(T, U, decltype([](T x, T y) { return x + y; }) func);
g(1, 2, [](int x, int y) { return x + y; });
The type of the lambda in the declaration and the call are different(by definition) and therefore this can not work.
Name mangling also becomes a problem since once you allow a lambda in a function signature the bodies of the lambda will have to be mangled as well. This means coming up with rules to mangle every possible statement, which would burdensome for at least some implementations.

Can anyone explain this paragraph of the current C++0x standard draft?

Can anyone explain this statement from ISO N3242 §3.2, 2nd point
An expression is potentially evaluated unless it is an unevaluated operand
(Clause 5) or a subexpression thereof. A variable or non-overloaded
function whose name appears as a potentially-evaluated expression is
odr-used unless it is an object that satisfies the requirements for appearing in a
constant
expression (5.19) and the lvalue-to-rvalue conversion (4.1) is
immediately
applied. this is odr-used if it appears as a potentiallyevaluated
expression
(including as the result of the implicit transformation in the body of
a
non-static member function (9.3.1)).
ISO Standard 2003 : says
An expression is potentially evaluated unless it appears where an
integral
constant expression is required (see 5.19), is the operand of the
sizeof
operator (5.3.3), or is the operand of the typeid operator and the
expression
does not designate an lvalue of polymorphic class type (5.2.8). An
object or
non-overloaded function is used if its name appears in a
potentially-evaluated
expression.
What is the actual difference in these statements?
Can any one explain this with the help of an example/program?
"unevaluated operand" replaces "is the operand of the sizeof operator (5.3.3), or is the operand of the typeid operator and the expression does not designate an lvalue of polymorphic class type (5.2.8)". It has the same basic purpose, but doesn't try to list all the cases in the C++0x standard of operators whose operands aren't evaluated. decltype is a new one, for example.
"odr-used" replaces "used", I presume they figured that "used" alone might be ambiguous with other uses of the word "use" in the standard. In both cases, though, it's defining the sense of "used" which is relevant to the ODR.
So those aren't really changes, just re-wordings updated for C++0x.
This is a change:
A variable or non-overloaded function
whose name appears as a
potentially-evaluated expression is
odr-used unless it is an object
that satisfies the requirements for
appearing in a constant expression
(5.19) and the lvalue-to-rvalue
conversion (4.1) is immediately
applied.
vs.
An object or non-overloaded
function is used if its name appears
in a potentially-evaluated
expression.
Suppose a is a static const int at global scope. Then in C++03 it is not used in the following statement:
char x[a];
because the context requires a constant expression. However, it is used in the following:
void foo(int); foo(a);
because the context doesn't require a constant expression.
In C++0x, a is not odr-used in either case. It's allowed to be in a constant expression, and in the function call, lvalue-rvalue conversion is immediately applied (because foo takes its parameter by value, not reference). So it qualifies for the "unless" which wasn't present in C++03.
There's also a difference in the definition of "potentially evaluated". In the first example, char x[a], a is potentially evaluated in C++03 but not in C++0x. I haven't checked whether anything else in the standard uses "potentially evaluated", that might be affected by this change. If it's only mentioned here then that part of it isn't a change, it's just that the exception has been moved from "potentially evaluated" to "used".