From 9f7f23c64b23799a35aa1610acb405b529f5d998 Mon Sep 17 00:00:00 2001 From: Jens Maurer Date: Mon, 2 Dec 2024 17:57:35 +0100 Subject: [PATCH] P2686R5 constexpr structured bindings and references to constexpr variables - Merge the change to [temp.arg.nontype] with existing text. --- source/basic.tex | 63 +++++++--- source/declarations.tex | 32 +++-- source/expressions.tex | 267 ++++++++++++++++++++++++++++++++-------- source/templates.tex | 29 +++-- 4 files changed, 309 insertions(+), 82 deletions(-) diff --git a/source/basic.tex b/source/basic.tex index c1708584a4..5a62cbb2c3 100644 --- a/source/basic.tex +++ b/source/basic.tex @@ -441,24 +441,59 @@ A variable is named by an expression if the expression is an \grammarterm{id-expression} that denotes it. A variable \tcode{x} that is named by a -potentially-evaluated expression $E$ -is \defnx{odr-used}{odr-use} by $E$ unless +potentially-evaluated expression $N$ +that appears at a point $P$ +is \defnx{odr-used}{odr-use} by $N$ unless \begin{itemize} \item - \tcode{x} is a reference that is - usable in constant expressions\iref{expr.const}, or +\tcode{x} is a reference +that is usable in constant expressions at $P$\iref{expr.const} or \item - \tcode{x} is a variable of non-reference type that is - usable in constant expressions and has no mutable subobjects, and - $E$ is an element of the set of potential results of an expression - of non-volatile-qualified non-class type - to which the lvalue-to-rvalue conversion\iref{conv.lval} is applied, or +$N$ is an element of the set of potential results of an expression $E$, where +\begin{itemize} +\item +$E$ is a discarded-value expression\iref{expr.context} +to which the lvalue-to-rvalue conversion is not applied or +\item +\tcode{x} is a non-volatile object +that is usable in constant expressions at $P$ and +has no mutable subobjects and +\begin{itemize} +\item +$E$ is a class member access expression\iref{expr.ref} +naming a non-static data member of reference type and +whose object expression has non-volatile-qualified type or \item - \tcode{x} is a variable of non-reference type, and - $E$ is an element of the set of potential results - of a discarded-value expression\iref{expr.context} - to which the lvalue-to-rvalue conversion is not applied. +the lvalue-to-rvalue conversion\iref{conv.lval} is applied to $E$ and +$E$ has non-volatile-qualified non-class type \end{itemize} +\end{itemize} +\end{itemize} +\begin{example} +\begin{codeblock} +int f(int); +int g(int&); +struct A { + int x; +}; +struct B { + int& r; +}; +int h(bool cond) { + constexpr A a = {1}; + constexpr const volatile A& r = a; // odr-uses \tcode{a} + int _ = f(cond ? a.x : r.x); // does not odr-use \tcode{a} or \tcode{r} + int x, y; + constexpr B b1 = {x}, b2 = {y}; // odr-uses \tcode{x} and \tcode{y} + int _ = g(cond ? b1.r : b2.r); // does not odr-use \tcode{b1} or \tcode{b2} + int _ = ((cond ? x : y), 0); // does not odr-use \tcode{x} or \tcode{y} + return [] { + return b1.r; // error: \tcode{b1} is odr-used here because the object + // referred to by \tcode{b1.r} is not constexpr-referenceable here + }(); +} +\end{codeblock} +\end{example} \pnum A structured binding is odr-used if it appears as a potentially-evaluated expression. @@ -6886,7 +6921,7 @@ \pnum \indextext{initialization!constant}% \defnx{Constant initialization}{constant initialization} is performed -if a variable or temporary object with static or thread storage duration +if a variable with static or thread storage duration is constant-initialized\iref{expr.const}. \indextext{initialization!zero-initialization}% If constant initialization is not performed, a variable with static diff --git a/source/declarations.tex b/source/declarations.tex index e540a1b209..6ec1fc7f21 100644 --- a/source/declarations.tex +++ b/source/declarations.tex @@ -213,6 +213,8 @@ a \defn{structured binding declaration}\iref{dcl.struct.bind}. Each \grammarterm{decl-specifier} in the \grammarterm{decl-specifier-seq} shall be +\tcode{constexpr}, +\tcode{constinit}, \tcode{static}, \tcode{thread_local}, \tcode{auto}\iref{dcl.spec.auto}, or @@ -850,7 +852,8 @@ \pnum The \keyword{constexpr} specifier shall be applied only to -the definition of a variable or variable template or +the definition of a variable or variable template, +a structured binding declaration, or the declaration of a function or function template. The \keyword{consteval} specifier shall be applied only to the declaration of a function or function template. @@ -995,9 +998,7 @@ Such an object shall have literal type and shall be initialized. -In any \keyword{constexpr} variable declaration, -the full-expression of the initialization -shall be a constant expression\iref{expr.const}. +A \keyword{constexpr} variable shall be constant-initializable\iref{expr.const}. A \keyword{constexpr} variable that is an object, as well as any temporary to which a \keyword{constexpr} reference is bound, shall have constant destruction. @@ -1008,6 +1009,16 @@ }; constexpr pixel ur = { 1294, 1024 }; // OK constexpr pixel origin; // error: initializer missing + +namespace N { + void f() { + int x; + constexpr int& ar = x; // OK + static constexpr int& sr = x; // error: \tcode{x} is not constexpr-representable + // at the point indicated below + } + // immediate scope here is that of \tcode{N} +} \end{codeblock} \end{example} @@ -1016,7 +1027,12 @@ \pnum The \keyword{constinit} specifier shall be applied only -to a declaration of a variable with static or thread storage duration. +to a declaration of a variable with static or thread storage duration +or to a structured binding declaration\iref{dcl.struct.bind}. +\begin{note} +A structured binding declaration introduces a uniquely named variable, +to which the \tcode{constinit} specifier applies. +\end{note} If the specifier is applied to any declaration of a variable, it shall be applied to the initializing declaration. No diagnostic is required if no \keyword{constinit} declaration @@ -7030,8 +7046,10 @@ appertains to the structured binding so introduced. Let \cv{} denote the \grammarterm{cv-qualifier}{s} in the \grammarterm{decl-specifier-seq} and -\placeholder{S} consist of the \grammarterm{storage-class-specifier}{s} of -the \grammarterm{decl-specifier-seq} (if any). +\placeholder{S} consist of +each \grammarterm{decl-specifier} of the \grammarterm{decl-specifier-seq} +that is \tcode{constexpr}, \tcode{constinit}, or +a \grammarterm{storage-class-specifier}. A \cv{} that includes \tcode{volatile} is deprecated; see~\ref{depr.volatile.type}. First, a variable with a unique name \exposid{e} is introduced. If the diff --git a/source/expressions.tex b/source/expressions.tex index e8664a3cc4..9ddc73d41b 100644 --- a/source/expressions.tex +++ b/source/expressions.tex @@ -7449,27 +7449,140 @@ \end{bnf} \pnum -A variable or temporary object \tcode{o} is \defn{constant-initialized} if +The \defnx{constituent values}{constituent value} of an object $o$ are \begin{itemize} \item - either it has an initializer or - its default-initialization results in some initialization being performed, and -\item - the full-expression of its initialization is a constant expression - when interpreted as a \grammarterm{constant-expression}, - except that if \tcode{o} is an object, - that full-expression - may also invoke constexpr constructors - for \tcode{o} and its subobjects - even if those objects are of non-literal class types. - \begin{note} - Such a class can have a non-trivial destructor. - Within this evaluation, - \tcode{std::is_constant_evaluated()}\iref{meta.const.eval} - returns \keyword{true}. +if $o$ has scalar type, the value of $o$; +\item +otherwise, the constituent values of any direct subobjects of $o$ +other than inactive union members. +\end{itemize} +The \defnx{constituent references}{constituent reference} of an object $o$ are +\begin{itemize} +\item +any direct members of $o$ that have reference type, and +\item +the constituent references of any direct subobjects of $o$ +other than inactive union members. +\end{itemize} + +\pnum +The constituent values and constituent references of +a variable \tcode{x} are defined as follows: +\begin{itemize} +\item +If \tcode{x} declares an object, +the constituent values and references of that object are +constituent values and references of \tcode{x}. +\item +If \tcode{x} declares a reference, +that reference is a constituent reference of \tcode{x}. +\end{itemize} +For any constituent reference \tcode{r} of a variable \tcode{x}, +if \tcode{r} is bound to a temporary object or subobject thereof +whose lifetime is extended to that of \tcode{r}, +the constituent values and references of that temporary object +are also constituent values and references of \tcode{x}, recursively. + +\pnum +An object $o$ is \defn{constexpr-referenceable} from a point $P$ if +\begin{itemize} +\item +$o$ has static storage duration, or +\item +$o$ has automatic storage duration, and, letting \tcode{v} denote +\begin{itemize} +\item +the variable corresponding to $o$'s complete object or +\item +the variable to whose lifetime that of $o$ is extended, +\end{itemize} +the smallest scope enclosing \tcode{v} and the smallest scope enclosing $P$ +that are neither +\begin{itemize} +\item +block scopes nor +\item +function parameter scopes associated with +a \grammarterm{requirement-parameter-list} +\end{itemize} +are the same function parameter scope. +\end{itemize} +\begin{example} +\begin{codeblock} +struct A { + int m; + const int& r; +}; +void f() { + static int sx; + thread_local int tx; // \tcode{tx} is never constexpr-referenceable + int ax; + A aa = {1, 2}; + static A sa = {3, 4}; + // The objects \tcode{sx}, \tcode{ax}, and \tcode{aa.m}, \tcode{sa.m}, and the temporaries to which \tcode{aa.r} and \tcode{sa.r} are bound, are constexpr-referenceable. + auto lambda = [] { + int ay; + // The objects \tcode{sx}, \tcode{sa.m}, and \tcode{ay} (but not \tcode{ax} or \tcode{aa}), and the + // temporary to which \tcode{sa.r} is bound, are constexpr-referenceable. + }; +} +\end{codeblock} +\end{example} + +\pnum +An object or reference \tcode{x} is +\defn{constexpr-representable} at a point $P$ if, +for each constituent value of \tcode{x} that points to or past an object $o$, +and for each constituent reference of \tcode{x} that refers to an object $o$, +$o$ is constexpr-referenceable from $P$. + +\pnum +A variable \tcode{v} is \defn{constant-initializable} if +\begin{itemize} +\item +the full-expression of its initialization is a constant expression +when interpreted as a \grammarterm{constant-expression}, +\begin{note} +Within this evaluation, +\tcode{std::is_constant_evaluated()}\iref{meta.const.eval} +returns \keyword{true}. \end{note} +and +\item +immediately after the initializing declaration of \tcode{v}, +the object or reference \tcode{x} declared by \tcode{v} +is constexpr-representable, and +\item +if \tcode{x} has static or thread storage duration, +\tcode{x} is constexpr-representable at the nearest point +whose immediate scope is a namespace scope +that follows the initializing declaration of \tcode{v}. \end{itemize} +\pnum +A constant-initializable variable is \defn{constant-initialized} +if either it has an initializer or +its default-initialization results in some initialization being performed. +\begin{example} +\begin{codeblock} +void f() { + int ax = 0; // \tcode{ax} is constant-initialized + thread_local int tx = 0; // \tcode{tx} is constant-initialized + static int sx; // \tcode{sx} is not constant-initialized + static int& rss = sx; // \tcode{rss} is constant-initialized + static int& rst = tx; // \tcode{rst} is not constant-initialized + static int& rsa = ax; // \tcode{rsa} is not constant-initialized + thread_local int& rts = sx; // \tcode{rts} is constant-initialized + thread_local int& rtt = tx; // \tcode{rtt} is not constant-initialized + thread_local int& rta = ax; // \tcode{rta} is not constant-initialized + int& ras = sx; // \tcode{ras} is constant-initialized + int& rat = tx; // \tcode{rat} is not constant-initialized + int& raa = ax; // \tcode{raa} is constant-initialized +} +\end{codeblock} +\end{example} + \pnum A variable is \defn{potentially-constant} if it is constexpr or @@ -7484,16 +7597,43 @@ \item $V$ is not initialized to a TU-local value, or \item $P$ is in the same translation unit as $D$. \end{itemize} -An object or reference is \defn{usable in constant expressions} if it is +An object or reference is +\defn{potentially usable in constant expressions} at point $P$ if it is \begin{itemize} -\item a variable that is usable in constant expressions, or -\item a template parameter object\iref{temp.param}, or -\item a string literal object\iref{lex.string}, or -\item a temporary object of non-volatile const-qualified literal type - whose lifetime is extended\iref{class.temporary} - to that of a variable that is usable in constant expressions, or -\item a non-mutable subobject or reference member of any of the above. +\item +the object or reference declared by a variable +that is usable in constant expressions at $P$, +\item +a temporary object of non-volatile const-qualified literal type +whose lifetime is extended\iref{class.temporary} +to that of a variable that is usable in constant expressions at $P$, +\item +a template parameter object\iref{temp.param}, +\item +a string literal object\iref{lex.string}, +\item +a non-mutable subobject of any of the above, or +\item +a reference member of any of the above. \end{itemize} +An object or reference is \defn{usable in constant expressions} at point $P$ +if it is an object or reference +that is potentially usable in constant expressions at $P$ and +is constexpr-representable at $P$. +\begin{example} +\begin{codeblock} +struct A { + int* const & r; +}; +void f(int x) { + constexpr A a = {&x}; + static_assert(a.r == &x); // OK + [&] { + static_assert(a.r != nullptr); // error: \tcode{a.r} is not usable in constant expressions at this point + }(); +} +\end{codeblock} +\end{example} \pnum An expression $E$ is a \defnadj{core constant}{expression} @@ -7817,6 +7957,45 @@ The copy/move of the active member is trivial. \end{note} +\pnum +For the purposes of determining whether $E$ is a core constant expression, +the evaluation of an \grammarterm{id-expression} +that names a structured binding \tcode{v}\iref{dcl.struct.bind} has the +following semantics: +\begin{itemize} +\item +If \tcode{v} is an lvalue referring to the object bound to an invented reference \tcode{r}, +the behavior is as if \tcode{r} were nominated. +\item +Otherwise, if \tcode{v} names an array element or class member, +the behavior is that of +evaluating \tcode{$e$[$i$]} or \tcode{$e$.$m$}, respectively, +where $e$ is the name of the variable +initialized from the initializer of the structured binding declaration, and +$i$ is the index of the element referred to by \tcode{v} or +$m$ is the name of the member referred to by \tcode{v}, respectively. +\end{itemize} +\begin{example} +\begin{codeblock} +#include +struct S { + mutable int m; + constexpr S(int m): m(m) {} + virtual int g() const; +}; +void f(std::tuple t) { + auto [r] = t; + static_assert(r.g() >= 0); // error: dynamic type is constexpr-unknown + constexpr auto [m] = S(1); + static_assert(m == 1); // error: lvalue-to-rvalue conversion on mutable + // subobject \tcode{e.m}, where \tcode{e} is a constexpr object of type \tcode{S} + using A = int[2]; + constexpr auto [v0, v1] = A{2, 3}; + static_assert(v0 + v1 == 5); // OK, equivalent to \tcode{e[0] + e[1]} where \tcode{e} is a constexpr array +} +\end{codeblock} +\end{example} + \pnum During the evaluation of an expression $E$ as a core constant expression, all \grammarterm{id-expression}s and uses of \tcode{*\keyword{this}} @@ -7973,40 +8152,20 @@ \pnum A \defnadj{constant}{expression} is either a glvalue core constant expression that refers to -an entity that is a permitted result of a constant expression (as defined below), or +an object or a non-immediate function, or a prvalue core constant expression whose value satisfies the following constraints: \begin{itemize} - \item - if the value is an object of class type, - each non-static data member of reference type refers to - an entity that is a permitted result of a constant expression, - - \item - if the value is an object of scalar type, - it does not have an indeterminate or erroneous value\iref{basic.indet}, - - \item - if the value is of pointer type, it is - a pointer to an object with static storage duration, - a pointer past the end of such an object\iref{expr.add}, - a pointer to a non-immediate function, - or a null pointer value, - - \item - if the value is of pointer-to-member-function type, - it does not designate an immediate function, and - - \item - if the value is an object of class or array type, - each subobject satisfies these constraints for the value. +\item +each constituent reference refers to an object or a non-immediate function, +\item +no constituent value of scalar type is an indeterminate value\iref{basic.indet}, +\item +no constituent value of pointer type is a pointer to an immediate function or +an invalid pointer value\iref{basic.compound}, and +\item +no constituent value of pointer-to-member type designates an immediate function. \end{itemize} -An entity is a -\defnx{permitted result of a constant expression}{constant expression!permitted result of} -if it is an -object with static storage duration that either is not a temporary object or is -a temporary object whose value satisfies the above constraints, or if -it is a non-immediate function. \begin{note} A glvalue core constant expression that either refers to or points to an unspecified object diff --git a/source/templates.tex b/source/templates.tex index 251afa1c4c..d7200b0ced 100644 --- a/source/templates.tex +++ b/source/templates.tex @@ -1149,20 +1149,22 @@ \rSec2[temp.arg.nontype]{Template non-type arguments} \pnum -If the type \tcode{T} of a \grammarterm{template-parameter}\iref{temp.param} -contains a placeholder type\iref{dcl.spec.auto} -or a placeholder for a deduced class type\iref{dcl.type.class.deduct}, -the type of the parameter is the type deduced -for the variable \tcode{x} in the invented declaration +A template argument $E$ for +a non-type \grammarterm{template-parameter} with declared type \tcode{T} +shall be such that the invented declaration \begin{codeblock} T x = @$E$@ ; \end{codeblock} -where $E$ is the template argument provided for the parameter. +satisfies the semantic constraints for the definition of +a \tcode{constexpr} variable with static storage duration\iref{dcl.constexpr}. +If \tcode{T} contains a placeholder type\iref{dcl.spec.auto} +or a placeholder for a deduced class type\iref{dcl.type.class.deduct}, +the type of the parameter is deduced from the above declaration. \begin{note} $E$ is a \grammarterm{template-argument} or (for a default template argument) an \grammarterm{initializer-clause}. \end{note} -If a deduced parameter type is not permitted +If the parameter type thus deduced is not permitted for a \grammarterm{template-parameter} declaration\iref{temp.param}, the program is ill-formed. @@ -1224,6 +1226,19 @@ \item a subobject\iref{intro.object} of one of the above. \end{itemize} +\pnum +\begin{example} +\begin{codeblock} +template class A{}; +extern int x; +A a; // OK +void f(int p) { + constexpr int& r = p; // OK + A a; // error: a static constexpr \tcode{int\&} variable cannot be initialized to refer to \tcode{p} here +} +\end{codeblock} +\end{example} + \pnum \begin{example} \begin{codeblock}