C++ integral constant expression definition - c++

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.

Related

Undefined behavior of constexpr static cast from int to scoped enum with non-fixed underlying type compiles in C++17

I wonder if the following should or should not compile in C++17
enum class E
{
A, B
};
constexpr E x = static_cast<E>(2);
This compiles with both GCC 9.3.0 and Clang 10.0.0 on Ubuntu 20.04.
My question is
Should this compile?
If it should, why?
I don’t think it should.
There are many posts regarding undefined behaviour (UB) and enums, but I couldn’t find any that brought it up in a constant expression context. Also, most posts use an underlying type, I consider scoped enums without any fixed underlying type. Since I don’t have access to a copy of an ISO standard, I will refer to the latest C++17 draft found a cppreference.com here in the reasoning below.
First, we find the paragraph that discards expressions with UB from being constant expressions
[expr.const]/2: An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:
with subsection
[expr.const]/2.6: an operation that would have undefined behavior as specified in Clauses [intro] through [cpp] of this International Standard [ Note: including, for example, signed integer overflow (Clause [expr]), certain pointer arithmetic ([expr.add]), division by zero, or certain shift operations  — end note ] ;
which tells us that constant expressions may not contain UB.
Then, we find the section regarding static casts from int to enum
[expr.static.cast]/10: A value of integral or enumeration type can be explicitly converted to a complete enumeration type. The value is unchanged if the original value is within the range of the enumeration values ([dcl.enum]). Otherwise, the behavior is undefined. A value of floating-point type can also be explicitly converted to an enumeration type. The resulting value is the same as converting the original value to the underlying type of the enumeration ([conv.fpint]), and subsequently to the enumeration type.
which tells us that the result of a static cast is well defined if the operand is within the range of the target enum. The section refers to [decl.enum]/8 regarding the range of the enumeration values (too long to post here, I can't get the formatting right either). Anyways, it says that the range of valid values of an enum of non-fixed underlying type is defined by the smallest bitset that can fit all values between the smallest and the biggest enumeration (in two-complements format).
Finally, applying these three sections on the example code, we can say that the smallest bitfield that can contain both A = 0 and B = 1 is of size one. Hence, the integer 2 (which requires two bits to represent in two-complements format) is outside the range of the target enum E, and the static cast has undefined behaviour. Since a constexpr variable can’t have undefined behaviour, the program should not compile.
The closest I could get to the answer is in this post where they discuss whether the compiler is required to issue a diagnostic in the case of UB in constexpr:
TLDR of that post is: Yes, but a warning might suffice to be complaint with the standard.
Scoped enums always have fixed underlying type. [dcl.enum]/5 (C++17):
For a scoped enumeration type, the underlying type is int if it is not explicitly specified. In both of these cases, the underlying type is said to be fixed.
So your E has fixed underlying type of int. Then in paragraph 8:
For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type.
2 is in range for int, so by the text you quoted from [expr.static.cast], the behaviour of the cast is well-defined.

"full-expression" versus "full expression"

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.

Are narrowing conversions in non-type template parameters required to be diagnosed?

This is almost assuredly a duplicate of Is gcc wrong not diagnose narrowing conversions in non-type template arguments? but it does not provide a satisfactory answer. The answer doesn't address whether or not GCC is wrong in not providing a diagnostic, only giving a warning flag that's somewhat a substitute.
The relevant sections of the standard:
§ 14.3.2/5
For a non-type template-parameter of integral or enumeration type,
conversions permitted in a con- verted constant expression (5.19) are
applied.
§ 5.19/3
A converted constant expression of type T is a literal constant
expression, implicitly converted to type T, where the implicit
conversion (if any) is permitted in a literal constant expression and
the implicit conversion sequence contains only user-defined
conversions, lvalue-to-rvalue conversions (4.1), integral promotions
(4.5), and integral conversions (4.7) other than narrowing conversions
(8.5.4)
The standard addresses situations in which narrowing conversions will make a program ill-formed, but there is a glaring omission here, so compilers disagree (GCC provides no diagnostic, Clang makes this an error, MSVC also provides no diagnostic.) Is there language in the standard that says for this situation, a diagnostic is required (i.e. it's ill-formed)?
#include <array>
int main()
{
std::array<int, -1> a;
}
As of n3337, the wording had already been changed to make it clear that the result is ill-formed. (§14.3.2/5):
The following conversions are performed on each expression used as a non-type template-argument. If a non-type template-argument cannot be converted to the type of the corresponding template-parameter then the program is ill-formed.
Since it specifies that the program is ill-formed, and doesn't give specific permission to the contrary, a diagnostic is required for violating this (per §1.4/1):
The set of diagnosable rules consists of all syntactic and semantic rules in this International Standard except for those rules containing an explicit notation that “no diagnostic is required” or which are described as resulting in “undefined behavior.”
FWIW, note that they've also separated the definition of the permissible conversions from those for converted constant expressions--the possible conversions are now listed directly in §14.3.2/5.

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".