Is "static const T x" equal to "static constexpr T x"?

127 views
Skip to first unread message

Tadeus Prastowo

unread,
Feb 28, 2019, 12:28:55 AM2/28/19
to std-dis...@isocpp.org
Hi,

For the following example:

template<const double *x>
struct X {
static constexpr double value = *x * 2;
};
static const double x = 2;
static_assert(X<&x>::value > 2);

could someone help me see where in the standard it is allowed to treat
"static const double x = 2" as if it were written as "static constexpr
double x = 2", please?

I ask this because Clang 6.0 and GCC 8.3 behave differently:
https://www.godbolt.org/z/dXIF3w shows that Clang rejects but GCC
accepts. However, when I change "double" to "int", they both accept:
https://www.godbolt.org/z/6AlZvk

Thank you very much.

--
Best regards,
Tadeus

Alberto Barbati

unread,
Feb 28, 2019, 11:58:57 AM2/28/19
to ISO C++ Standard - Discussion
If x is double, it is not "usable in a constant expression", due to [expr.const]/3 (emphasis added):

A variable is usable in constant expressions after its initializing declaration is encountered if it is a constexpr
variable, or it is of reference type or of const-qualified integral or enumeration type, and its initializer is a
constant initializer.

Therefore I believe GCC is wrong and shoud reject the code when x is double. On the other hand, both Clang and GCC are correct to accept the code if x is int.

Alberto

Tadeus Prastowo

unread,
Mar 1, 2019, 5:49:49 AM3/1/19
to std-dis...@isocpp.org
On Thu, Feb 28, 2019 at 5:58 PM Alberto Barbati
<alberto...@gmail.com> wrote:
>
> If x is double, it is not "usable in a constant expression", due to [expr.const]/3 (emphasis added):
>
>> A variable is usable in constant expressions after its initializing declaration is encountered if it is a constexpr
>> variable, or it is of reference type or of const-qualified integral or enumeration type, and its initializer is a
>> constant initializer.

Thank you very much for pointing this out. The passage is missing
from https://wg21.link/n4659, the C++17 final draft
(http://www.open-std.org/JTC1/SC22/WG21/docs/standards.html). But, it
exists in the latest working draft:
http://eel.is/c++draft/expr.const#3

Digging the git repository of the working draft
(https://github.com/cplusplus/draft), I see that the passage was
introduced at the following commit, which implements
https://wg21.link/p0595r2:
-- 8<----------------------------------------
commit 9eccd8d1d2e217e770c761c1f3e82267e9f4c5a5
Author: Jens Maurer <Jens....@gmx.net>
Date: Mon Nov 12 14:15:15 2018 +0100

P0595R2 std::is_constant_evaluated()

source/basic.tex | 8 +-------
source/expressions.tex | 78
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
source/support.tex | 2 ++
source/utilities.tex | 28 ++++++++++++++++++++++++++++
4 files changed, 94 insertions(+), 22 deletions(-)
-- 8<----------------------------------------

The relevant changes to source/expressions.tex that gave birth to the
new passage are as follows (for the complete changeset, see
https://github.com/cplusplus/draft/commit/9eccd8d1d2e217e770c761c1f3e82267e9f4c5a5):
-- 8<----------------------------------------
diff --git a/source/expressions.tex b/source/expressions.tex
index 4a84068..157043b 100644
--- a/source/expressions.tex
+++ b/source/expressions.tex
@@ -6558,20 +6580,8 @@ it is applied to

\begin{itemize}
\item
- a non-volatile glvalue of integral or enumeration type that refers
- to a complete non-volatile const object with a preceding initialization,
- initialized with a constant expression, or
-
- \item
- a non-volatile glvalue that refers to a subobject of a string
- literal\iref{lex.string}, or
-
- \item
- a non-volatile glvalue that refers to a non-volatile object
- defined with \tcode{constexpr} or
- a template parameter object\iref{temp.param},
- or that refers to a non-mutable subobject
- of such an object, or
+ a non-volatile glvalue that refers to an object that is
+ usable in constant expressions, or

\item
a non-volatile glvalue of literal type that refers to a non-volatile object
-- 8<----------------------------------------

As you can see, the relevant passage for the `-std=c++17' switch of
the compilers is as follows, quoting https://wg21.link/n4659
[expr.const]p2:
-- 8<----------------------------------------
An expression e is a core constant expression unless the evaluation of
e, following the rules of the abstract machine (4.6), would evaluate
one of the following expressions:
[...]
— an lvalue-to-rvalue conversion (7.1) unless it is applied to
— a non-volatile glvalue of integral or enumeration type that
refers to a complete non-volatile const object with a preceding
initialization, initialized with a constant expression
-- 8<----------------------------------------

However, the following expression from my example in the first post
does not constitute an lvalue-to-rvalue conversion.
-- 8<----------------------------------------
&x
// Taken from: static_assert(X<&x>::value > 2);
-- 8<----------------------------------------

Quoting https://wg21.link/n4659 [expr.unary.op]p2,3:
-- 8<----------------------------------------
The result of each of the following unary operators is a prvalue.
The result of the unary & operator is a pointer to its operand. The
operand shall be an lvalue or a qualified-id. [...]
-- 8<----------------------------------------

So, "&x" requires no conversion whatsoever on the subexpression "x".
Furthermore, the resulting address is a prvalue, but it is not by
converting "x". The C++17 standard also agrees. Quoting
https://wg21.link/n4659 [conv]p8:
-- 8<----------------------------------------
[ Note: There are some contexts where certain conversions are
suppressed. For example, the lvalue-to-rvalue conversion is not done
on the operand of the unary & operator. Specific exceptions are given
in the descriptions of those operators and contexts. — end note ]
-- 8<----------------------------------------

> Therefore I believe GCC is wrong and shoud reject the code when x is double. On the other hand, both Clang and GCC are correct to accept the code if x is int.

I am sorry that I do not say it in the beginning that the compilations
are to be performed using C++17 standard.

So, since the compilations are both performed using `-std=c++17',
where in the C++17 can I show that "static const T x" is or is not
equal to "static constexpr T x"?

Thank you very much for your kind help.

> Alberto

--
Best regards,
Tadeus

Alberto Barbati

unread,
Mar 1, 2019, 6:02:03 AM3/1/19
to ISO C++ Standard - Discussion
Il giorno venerdì 1 marzo 2019 11:49:49 UTC+1, Tadeus Prastowo ha scritto:

I am sorry that I do not say it in the beginning that the compilations
are to be performed using C++17 standard.

So, since the compilations are both performed using `-std=c++17',
where in the C++17 can I show that "static const T x" is or is not
equal to "static constexpr T x"?

 I believe paragraph [expr.const]/2.7.1 in N4659 might by relevant.

Alberto

Tadeus Prastowo

unread,
Mar 1, 2019, 6:35:51 AM3/1/19
to std-dis...@isocpp.org, Jens....@gmx.net
@Jens Maurer: Kindly see the body of this e-mail.
But that is an exception given to an lvalue-to-rvalue conversion.
Since "&x" performs no lvalue-to-rvalue conversion, logically whether
"static const double x = 2" is or is not equal to "static constexpr
double x = 2" cannot be decided using that paragraph, can it?

@Jens Maurer: At commit
https://github.com/cplusplus/draft/commit/9eccd8d1d2e217e770c761c1f3e82267e9f4c5a5,
you introduce this new definition: http://eel.is/c++draft/expr.const#3
As far as I can see from the changeset, the new definition is
synthesized from the following three discarded points that exist in
C++17 standard document (https://wg21.link/n4659):
— a non-volatile glvalue of integral or enumeration type that refers
to a complete non-volatile const object with a preceding
initialization, initialized with a constant expression, or
— a non-volatile glvalue that refers to a subobject of a string
literal (5.13.5), or
— a non-volatile glvalue that refers to a non-volatile object defined
with constexpr, or that refers to a non-mutable subobject of such an
object, or
So, I think there is a logical gap in the synthesis: the discarded
points are an exception when an lvalue-to-rvalue takes place, but the
new definition removes that exception.

Currently, I am seeing two C++17-compliant compilers at
https://www.godbolt.org/z/dXIF3w
One compiler treats "static const double x = 2" as being equivalent to
"static constexpr double x = 2". The other compiler treats "static
const double x = 2" as being different from "static constexpr double x
= 2".

I am about to file a bug report to one of them. But to do so, I need
to know which part of C++17 standard says whether "static const double
x = 2" is or is not equivalent to "static constexpr double x = 2"
(C++17 standard does not have the new definition that you introduced).
Could you help, please?

Alberto Barbati

unread,
Mar 1, 2019, 6:46:01 AM3/1/19
to ISO C++ Standard - Discussion, Jens....@gmx.net
Il giorno venerdì 1 marzo 2019 12:35:51 UTC+1, Tadeus Prastowo ha scritto:
@Jens Maurer: Kindly see the body of this e-mail.

On Fri, Mar 1, 2019 at 12:02 PM Alberto Barbati
<alberto...@gmail.com> wrote:

>  I believe paragraph [expr.const]/2.7.1 in N4659 might by relevant.

But that is an exception given to an lvalue-to-rvalue conversion.
Since "&x" performs no lvalue-to-rvalue conversion, logically whether
"static const double x = 2" is or is not equal to "static constexpr
double x = 2" cannot be decided using that paragraph, can it?

&x doesn't perform lvalue-to-rvalue conversion, but the expression *x * 2 does it. In fact clang reports this error:

static constexpr double value = *x * 2;


Alberto

Tadeus Prastowo

unread,
Mar 1, 2019, 1:58:01 PM3/1/19
to std-dis...@isocpp.org, Jens Maurer
On Fri, Mar 1, 2019 at 12:46 PM Alberto Barbati
<alberto...@gmail.com> wrote:
>
> Il giorno venerdì 1 marzo 2019 12:35:51 UTC+1, Tadeus Prastowo ha scritto:
>>
>> @Jens Maurer: Kindly see the body of this e-mail.
>>
>> On Fri, Mar 1, 2019 at 12:02 PM Alberto Barbati
>> <alberto...@gmail.com> wrote:
>> >
>>
>> > I believe paragraph [expr.const]/2.7.1 in N4659 might by relevant.
>>
>> But that is an exception given to an lvalue-to-rvalue conversion.
>> Since "&x" performs no lvalue-to-rvalue conversion, logically whether
>> "static const double x = 2" is or is not equal to "static constexpr
>> double x = 2" cannot be decided using that paragraph, can it?
>
>
> &x doesn't perform lvalue-to-rvalue conversion, but the expression *x * 2 does it.

I have scoured the C++17 standard document (https://wg21.link/n4659)
for hours to find the statement that the expression *x * 2 is required
to perform an lvalue-to-rvalue conversion. Fortunately, I can find
the requirement as can be seen from the following steps:

1. The id-expression `x' is a non-type template-parameter of type
"pointer to const double". So, [temp.param]p6 says: A non-type
non-reference template-parameter is a prvalue.

The type of the prvalue is "pointer to const double" according to
[expr.prim.id.unqual]p1, which says: An identifier is an id-expression
provided it has been suitably declared (Clause 10). [...] The type
of the expression is the type of the identifier.

2. There is a unary operator `*'. So, [expr.unary.op]p1 says: The
unary * operator performs indirection: the expression to which it is
applied shall be a pointer to an object type, or a pointer to a
function type and the result is an lvalue referring to the object or
function to which the expression points. If the type of the
expression is “pointer to T”, the type of the result is “T”.

So, the subexpression `*x' is an lvalue without any conversion
involved; it is the result of an indirection operation. The type of
the lvalue is "const double".

3. Now [basic.fundamental]p8 says: There are three floating-point
types: float, double, and long double. [...] Integral and floating
types are collectively called arithmetic types.
Additionally, [basic.type.qualifier]p1 says: A type mentioned in 6.9.1
and 6.9.2 is a cv-unqualified type. Each type which is a
cv-unqualified complete or incomplete object type or is void (6.9) has
three corresponding cv-qualified versions of its type: a
const-qualified version, a volatile-qualified version, and a
const-volatile-qualified version. [...] The cv-qualified or
cv-unqualified versions of a type are distinct types; however, they
shall have the same representation and alignment requirements (6.11).

So, the type of the lvalue is a const-qualified arithmetic type.
Remember that [basic.type.qualifier]p1 says that "double" and "const
double" are distinct types.

4. There is the binary multiplicative operator `*'. So, [expr.mul]p2
says: The operands of * and / shall have arithmetic or unscoped
enumeration type; the operands of % shall have integral or unscoped
enumeration type. The usual arithmetic conversions are performed on
the operands and determine the type of the result.

5. There is a requirement for the usual arithmetic conversions to be
performed on the operands. So, [expr]p11 says: Many binary operators
that expect operands of arithmetic or enumeration type cause
conversions and yield result types in a similar way. The purpose is to
yield a common type, which is also the type of the result. This
pattern is called the usual arithmetic conversions, which are defined
as follows: [...] — Otherwise, if either operand is double, the other
shall be converted to double.

This requirement, however, cannot be executed at this point, in
particular because the left operand has type "const double", not a
type discussed in [expr]p11 considering point 3 above. To be
coherent, it must be done at point 11 below.

6. Now [over.match.oper]p1 says: If no operand of an operator in an
expression has a type that is a class or an enumeration, the operator
is assumed to be a built-in operator and interpreted according to
Clause 8.

Clause 8 says in [expr.mul]p3: The binary * operator indicates multiplication.

7. Now [over.match.oper]p3 says: For a unary operator @ with an
operand of a type whose cv-unqualified version is T1, and for a binary
operator @ with a left operand of a type whose cv-unqualified version
is T1 and a right operand of a type whose cv-unqualified version is
T2, three sets of candidate functions, designated member candidates,
non-member candidates and built-in candidates, are constructed as
follows:

So, for the subexpression `*x' as the left operand for a binary
operator @ where @ is *, T1 is "double". For the subexpression `2' as
the right operand for the same binary operator, T2 is "double" due to
the usual arithmetic conversion.

Recall that the left operand is lvalue (the right operand is a prvalue
as per [lex.icon]p1 and [expr.prim.literal]p1).

8. Continuing with [over.match.oper]p3, it says:
— If T1 is a complete class type or a class currently being defined,
the set of member candidates is the result of the qualified lookup of
T1::operator@ (16.3.1.1.1); otherwise, the set of member candidates is
empty.

Since T1 is "double", the set of member candidates is empty.

9. Continuing with [over.match.oper]p3, it says:
— The set of non-member candidates is the result of the unqualified
lookup of operator@ in the context of the expression according to the
usual rules for name lookup in unqualified function calls (6.4.2)
except that all member functions are ignored. However, if no operand
has a class type, only those non-member functions in the lookup set
that have a first parameter of type T1 or “reference to cv T1”, when
T1 is an enumeration type, or (if there is a right operand) a second
parameter of type T2 or “reference to cv T2”, when T2 is an
enumeration type, are candidate functions.

Since the program declares no operator*, the set of non-member
candidates is also empty.

10. Continuing with [over.match.oper]p3, it says:
— For the operator ,, the unary operator &, or the operator ->, the
built-in candidates set is empty. For all other operators, the
built-in candidates include all of the candidate operator functions
defined in 16.6 that, compared to the given operator,
— have the same operator name, and
— accept the same number of operands, and
— accept operand types to which the given operand or operands can
be converted according to 16.3.3.1, and
— do not have the same parameter-type-list as any non-member
candidate that is not a function template specialization.

11. Now 16.6 is [over.built]. There are two relevant paragraphs, p2
and p13, which say: In this subclause, the term promoted integral type
is used to refer to those integral types which are preserved by
integral promotion (7.6) (including e.g. int and long but excluding
e.g. char). Similarly, the term promoted arithmetic type refers to
floating types plus promoted integral types. [...] For every pair of
promoted arithmetic types L and R , there exist candidate operator
functions of the form LR operator*(L, R); where LR is the result of
the usual arithmetic conversions between types L and R.

So, the set of built-in candidates includes among other elements:
double operator*(double, int).

Note that based on point 3 and 4 above, all elements in the set have
L, R, and LR as cv-unqualified types.

12. Going back to point 10 above, 16.3.3.1 is [over.best.ics] whose
p1, p2, and, more importantly, p6 say: An implicit conversion sequence
is a sequence of conversions used to convert an argument in a function
call to the type of the corresponding parameter of the function being
called. The sequence of conversions is an implicit conversion as
defined in Clause 7, which means it is governed by the rules for
initialization of an object or reference by a single expression (11.6,
11.6.3). Implicit conversion sequences are concerned only with the
type, cv-qualification, and value category of the argument and how
these are converted to match the corresponding properties of the
parameter. Other properties, such as the lifetime, storage class,
alignment, accessibility of the argument, whether the argument is a
bit-field, and whether a function is deleted (11.4.3), are ignored.
So, although an implicit conversion sequence can be defined for a
given argument-parameter pair, the conversion from the argument to the
parameter might still be ill-formed in the final analysis. [...]
When the parameter type is not a reference, the implicit conversion
sequence models a copy-initialization of the parameter from the
argument expression. The implicit conversion sequence is the one
required to convert the argument expression to a prvalue of the type
of the parameter.

Since "double" is not a reference, "the implicit conversion sequence
is the one required to convert [`*x'] to a prvalue of [type double]".

Recall that `*x' at point 2 above is an lvalue. So, [over.best.ics]p6
is the stipulation that requires an lvalue-to-rvalue conversion, which
you already pointed out.

> In fact clang reports this error:
>
> <source>:3:27: error: constexpr variable 'value' must be initialized by a constant expression
>
> static constexpr double value = *x * 2;

Yes, and in doing so, Clang is correct as per my analysis above.

Thank you very much for your kind help.

I have filed a bug report to GCC at
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89553.

> Alberto

Have a nice weekend!

--
Best regards,
Tadeus

Language Lawyer

unread,
Mar 1, 2019, 2:19:14 PM3/1/19
to std-dis...@isocpp.org
If `*x * 2` is interpreted according to Clause 8 [expr], why do you continue to interpret it according to [over.match]?

Brian Bi

unread,
Mar 1, 2019, 2:37:22 PM3/1/19
to std-dis...@isocpp.org
According to [over.built]/1, the built-in operator candidates are only used "as described in 16.3.1.2 and for no other purpose" (i.e., [over.match.oper]), and according to [over.match.oper]/1, if none of the arguments have class or enumeration type, then the operator is interpreted as a built-in operator (and the overload resolution process is not used). Thus, reasoning about this overload resolution process cannot be used to establish that your expression `*x * 2` invokes an lvalue-to-rvalue conversion.

Instead, I believe that the fact that the lvalue-to-rvalue conversion is required must be inferred from the semantics of the * operator. As you indicated, the standard states that the * operator indicates multiplication. Multiplication operates on numerical values, but a glvalue of type `double` identifies a `double` object, and is not itself a numerical value. We must therefore infer that an lvalue-to-rvalue conversion is required here to obtain the stored value; otherwise, the multiplication could not be performed.

According to the C89 standard, "Except when it is the operand of the sizeof operator, the unary & operator, the ++ operator, the -- operator, or the left operand of the . operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue)." This rule, that lvalue-to-rvalue conversion occurs "eagerly", was not carried through to C++, and it appears that in the standardization process of C++, the committee simply overlooked the fact that there was no longer an explicit indication that the lvalue-to-rvalue conversion must be performed on the operands to arithmetic operators.

Perhaps the omission went unnoticed then because it was sufficiently obvious to those who knew C that an lvalue-to-rvalue conversion must occur here. Given the significant divergence between C and C++ in the intervening years (including the revision of value categories in C++11, and the redefinition of "prvalue" in C++17), I think it would be reasonable to open up an editorial issue asking for the standard text to explicitly state when the built-in operators require one or more of their operands to be prvalues.

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.


--
Brian Bi

Richard Smith

unread,
Mar 2, 2019, 12:04:45 AM3/2/19
to std-dis...@isocpp.org
On Fri, 1 Mar 2019 at 09:37, Brian Bi <bbi...@gmail.com> wrote:
>
> According to [over.built]/1, the built-in operator candidates are only used "as described in 16.3.1.2 and for no other purpose" (i.e., [over.match.oper]), and according to [over.match.oper]/1, if none of the arguments have class or enumeration type, then the operator is interpreted as a built-in operator (and the overload resolution process is not used). Thus, reasoning about this overload resolution process cannot be used to establish that your expression `*x * 2` invokes an lvalue-to-rvalue conversion.
>
> Instead, I believe that the fact that the lvalue-to-rvalue conversion is required must be inferred from the semantics of the * operator. As you indicated, the standard states that the * operator indicates multiplication. Multiplication operates on numerical values, but a glvalue of type `double` identifies a `double` object, and is not itself a numerical value. We must therefore infer that an lvalue-to-rvalue conversion is required here to obtain the stored value; otherwise, the multiplication could not be performed.
>
> According to the C89 standard, "Except when it is the operand of the sizeof operator, the unary & operator, the ++ operator, the -- operator, or the left operand of the . operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue)." This rule, that lvalue-to-rvalue conversion occurs "eagerly", was not carried through to C++, and it appears that in the standardization process of C++, the committee simply overlooked the fact that there was no longer an explicit indication that the lvalue-to-rvalue conversion must be performed on the operands to arithmetic operators.
>
> Perhaps the omission went unnoticed then because it was sufficiently obvious to those who knew C that an lvalue-to-rvalue conversion must occur here. Given the significant divergence between C and C++ in the intervening years (including the revision of value categories in C++11, and the redefinition of "prvalue" in C++17), I think it would be reasonable to open up an editorial issue asking for the standard text to explicitly state when the built-in operators require one or more of their operands to be prvalues.

This would not be editorial, and so a core issue should be filed
instead. And it has been: this is core issue 1642.

Tadeus Prastowo

unread,
Mar 2, 2019, 6:56:21 AM3/2/19
to std-dis...@isocpp.org
On Fri, Mar 1, 2019 at 8:19 PM Language Lawyer
<languag...@gmail.com> wrote:

[...]

> > 6. Now [over.match.oper]p1 says: If no operand of an operator in an
> > expression has a type that is a class or an enumeration, the operator
> > is assumed to be a built-in operator and interpreted according to
> > Clause 8.
>
> If `*x * 2` is interpreted according to Clause 8 [expr], why do you continue to interpret it according to [over.match]?

Because I understood "interpreted according to Clause 8" as to mean
the semantics of the operator * but not the technical details to apply
the evaluation of the operator.

That is wrong, of course, now that I remember that a C++ textbook
usually says, "no operator overloading is allowed for primitive
types".

So, the main reason I forgot that was because I could not find any
statement in Clause 8 [expr] concerning the value categories expected
by the multiplicative operators for their operands and the fact that
the paragraph about the usual arithmetic conversion says nothing on
the type cv-qualification.

--
Best regards,
Tadeus

Tadeus Prastowo

unread,
Mar 2, 2019, 7:05:26 AM3/2/19
to std-dis...@isocpp.org
On Fri, Mar 1, 2019 at 8:37 PM Brian Bi <bbi...@gmail.com> wrote:
>
> According to [over.built]/1, the built-in operator candidates are only used "as described in 16.3.1.2 and for no other purpose" (i.e., [over.match.oper]), and according to [over.match.oper]/1, if none of the arguments have class or enumeration type, then the operator is interpreted as a built-in operator (and the overload resolution process is not used). Thus, reasoning about this overload resolution process cannot be used to establish that your expression `*x * 2` invokes an lvalue-to-rvalue conversion.
>
> Instead, I believe that the fact that the lvalue-to-rvalue conversion is required must be inferred from the semantics of the * operator. As you indicated, the standard states that the * operator indicates multiplication. Multiplication operates on numerical values, but a glvalue of type `double` identifies a `double` object, and is not itself a numerical value. We must therefore infer that an lvalue-to-rvalue conversion is required here to obtain the stored value; otherwise, the multiplication could not be performed.
>
> According to the C89 standard, "Except when it is the operand of the sizeof operator, the unary & operator, the ++ operator, the -- operator, or the left operand of the . operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue)." This rule, that lvalue-to-rvalue conversion occurs "eagerly", was not carried through to C++, and it appears that in the standardization process of C++, the committee simply overlooked the fact that there was no longer an explicit indication that the lvalue-to-rvalue conversion must be performed on the operands to arithmetic operators.
>
> Perhaps the omission went unnoticed then because it was sufficiently obvious to those who knew C that an lvalue-to-rvalue conversion must occur here. Given the significant divergence between C and C++ in the intervening years (including the revision of value categories in C++11, and the redefinition of "prvalue" in C++17), I think it would be reasonable to open up an editorial issue asking for the standard text to explicitly state when the built-in operators require one or more of their operands to be prvalues.

Thank you very much for your comprehensive explanation, Brian. I
really appreciate it.

Now I understand that the requirement for the lvalue-to-rvalue
conversion is indeed not in the C++17 standard.

So, a compiler that claims to be in a strict conformance to the C++17
ISO standard can say that it indeed performs no lvalue-to-rvalue
conversion in its handling of the expression *x * 2. How can we
counter that claim formally based on the C++17 ISO standard?

Thank you.

> --
> Brian Bi

--
Best regards,
Tadeus
Message has been deleted

Tadeus Prastowo

unread,
Mar 3, 2019, 3:17:03 AM3/3/19
to std-dis...@isocpp.org
On Sat, Mar 2, 2019 at 6:04 AM Richard Smith <ric...@metafoo.co.uk> wrote:
>
> On Fri, 1 Mar 2019 at 09:37, Brian Bi <bbi...@gmail.com> wrote:
> > Perhaps the omission went unnoticed then because it was sufficiently obvious to those who knew C that an lvalue-to-rvalue conversion must occur here. Given the significant divergence between C and C++ in the intervening years (including the revision of value categories in C++11, and the redefinition of "prvalue" in C++17), I think it would be reasonable to open up an editorial issue asking for the standard text to explicitly state when the built-in operators require one or more of their operands to be prvalues.
>
> This would not be editorial, and so a core issue should be filed
> instead. And it has been: this is core issue 1642.

Thank you very much for your information.

It has been five years. What is the necessary push to get it accomplished?

--
Best regards,
Tadeus

Language Lawyer

unread,
Mar 3, 2019, 1:29:27 PM3/3/19
to std-dis...@isocpp.org
On 02/03/2019 08:04, Richard Smith wrote:
> On Fri, 1 Mar 2019 at 09:37, Brian Bi <bbi...@gmail.com> wrote:
>> Perhaps the omission went unnoticed then because it was sufficiently obvious to those who knew C that an lvalue-to-rvalue conversion must occur here. Given the significant divergence between C and C++ in the intervening years (including the revision of value categories in C++11, and the redefinition of "prvalue" in C++17), I think it would be reasonable to open up an editorial issue asking for the standard text to explicitly state when the built-in operators require one or more of their operands to be prvalues.
>
> This would not be editorial, and so a core issue should be filed
> instead. And it has been: this is core issue 1642.

I'm confused.
https://github.com/cplusplus/draft/wiki/How-to-tell-if-an-issue-is-editorial says that fixes which would not alter the intended meaning of the specification are editorial.

Saying that some operators (like multiplication operator here) require prvalue operands would change the intended meaning of the standard?

Richard Smith

unread,
Mar 4, 2019, 2:46:46 PM3/4/19
to std-dis...@isocpp.org
There's some subtlety here. Yes, officially, we could change anything
that doesn't alter the intended meaning under the guise of "editorial
change". But there are a couple of problems with that:

1) People (both in the committee and outside) become quite attached to
particular presentation styles, phrasings, etc., generally for good
reason (people internalize what certain things mean, wording papers
are written based on the current presentation and its style, CWG and
LWG spend large amounts of time working to arrive at wording that
intentionally avoids certain classes of misunderstanding that we might
overlook when rewording, and so on).
2) In a technical document such as the C++ standard, there is a huge
amount of nuance and subtlety, and any change introduces risk of
unintended changes of meaning or interpretation. Part of the problem
is that there is no single "intended meaning", and sometimes even for
things that we thought were universally understood to be the one
meaning, it turns out that there are some people who read the words a
different way and disagree strongly (and indeed any wording change
creates opportunity for more such alternate readings to emerge).

In this case, I'm not worried about the first point, but I am
concerned about the second. As a technical expert, I believe I know
exactly what the intended rule is and what it's supposed to mean
(briefly: unless otherwise specified, all operands that are not
arguments to an explicit or implicit function call are required to be
prvalues), but as the project editor, I am not comfortable assuming
that everyone agrees on that, nor that there are no mistakes in my
formulation of the rule (are there special cases, for instance for
bit-fields or unevaluated operands or something I didn't even think
of? are there any "unless otherwise specified" cases that we fail to
actually specify?). As a consequence, I'm generally hesitant to treat
a change such as this, that carries a non-trivial risk of making a
literal interpretation of the text differ subtly from the intended
interpretation, as editorial.
Reply all
Reply to author
Forward
0 new messages