Message ID | 20220322171858.976265-1-ppalka@redhat.com |
---|---|
State | New |
Headers |
Return-Path: <gcc-patches-bounces+patchwork=sourceware.org@gcc.gnu.org> X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id AD18B389EC60 for <patchwork@sourceware.org>; Tue, 22 Mar 2022 17:19:57 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org AD18B389EC60 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1647969597; bh=77NyFBtt5WFMgL1GQc0wif1BDZLdkUYJIpV0YCDXQtE=; h=To:Subject:Date:List-Id:List-Unsubscribe:List-Archive:List-Post: List-Help:List-Subscribe:From:Reply-To:From; b=qKc1BOqdiC8N5jLv8sHd3x8UhJGEReRp9AqJuWEQ1JJwfhFTvOwsYErr06hqFTDpG nurq2x5dVBiyj4stkKRt47OLIguDD265ggxcfbUmoOWu4OTW3PSNLiRrbiWNTaX/Zx +MffkwxACVFEZzxviSl9D8MSKEvnpR0YieL0S77s= X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id 3B35D3858407 for <gcc-patches@gcc.gnu.org>; Tue, 22 Mar 2022 17:19:24 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 3B35D3858407 Received: from mail-qv1-f71.google.com (mail-qv1-f71.google.com [209.85.219.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-384-Cfma3WPmNkOvqGmbqbCrkQ-1; Tue, 22 Mar 2022 13:19:20 -0400 X-MC-Unique: Cfma3WPmNkOvqGmbqbCrkQ-1 Received: by mail-qv1-f71.google.com with SMTP id fw9-20020a056214238900b0043522aa5b81so14217261qvb.21 for <gcc-patches@gcc.gnu.org>; Tue, 22 Mar 2022 10:19:20 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=77NyFBtt5WFMgL1GQc0wif1BDZLdkUYJIpV0YCDXQtE=; b=6GcBZcsE1aHWMmqyhgahnHsPfbyq0jcsGSCax9NkNZQvhivXKL97f7HD+7Ijfk0Ttf 2efAPtEwCml2t6faRc5fsqrm0zd+2HO5DYkF08cT65+POOkSYGfNKcQXbxNFbL2ylNZc abcR6rXkrCPsJGYLF2gSmrth4JsxpUcwHe1DcbDcUDbVoQprjZa8RH5wvlGair/cDwqB GFNqr4cvvEdl4hxDO87UXR7+MjBmrX7dyi935LxwA2qkO5iz9AbqGjOjoUxJ1HAWHJ7c rh3Jq7XkLncAoP0pWXJOMbMWqKO7kduvhksEePH0NUTWC1bQ0lq0IIWOHdbt8DhfKSK+ MnPA== X-Gm-Message-State: AOAM532SMwJ9I5V/XU0h+JnK44TCeQdpnHVGSVVEZfDbP+RILlrUgpjX fsCf+8F+R1kk4CxEfLOTdNk2WwtQfvKAtBix97K5VSW32e0W8PqvitaBZaRN5gOui6mYPGjbzz5 dcaqJLZg46GWjTSCKRWctqi/35ODiET0W00bg3MVWiH5owLQzPVSDhoX7IA1GTXo3RNY= X-Received: by 2002:ac8:4e52:0:b0:2e1:dad8:5141 with SMTP id e18-20020ac84e52000000b002e1dad85141mr20979523qtw.62.1647969559765; Tue, 22 Mar 2022 10:19:19 -0700 (PDT) X-Google-Smtp-Source: ABdhPJyVB8XlqWi2G6U9hQ4kTmvZRcmescAh7OPRjjdBmXzFQtk1Eh28fq1DVA1waDsk14M5DWvo5A== X-Received: by 2002:ac8:4e52:0:b0:2e1:dad8:5141 with SMTP id e18-20020ac84e52000000b002e1dad85141mr20979502qtw.62.1647969559423; Tue, 22 Mar 2022 10:19:19 -0700 (PDT) Received: from localhost.localdomain (ool-18e40894.dyn.optonline.net. [24.228.8.148]) by smtp.gmail.com with ESMTPSA id b126-20020a376784000000b0067d21404704sm9046674qkc.131.2022.03.22.10.19.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 22 Mar 2022 10:19:18 -0700 (PDT) To: gcc-patches@gcc.gnu.org Subject: [PATCH] c++: alias template arguments are evaluated [PR101906] Date: Tue, 22 Mar 2022 13:18:58 -0400 Message-Id: <20220322171858.976265-1-ppalka@redhat.com> X-Mailer: git-send-email 2.35.1.607.gf01e51a7cf MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="US-ASCII" X-Spam-Status: No, score=-14.4 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H5, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-patches mailing list <gcc-patches.gcc.gnu.org> List-Unsubscribe: <https://gcc.gnu.org/mailman/options/gcc-patches>, <mailto:gcc-patches-request@gcc.gnu.org?subject=unsubscribe> List-Archive: <https://gcc.gnu.org/pipermail/gcc-patches/> List-Post: <mailto:gcc-patches@gcc.gnu.org> List-Help: <mailto:gcc-patches-request@gcc.gnu.org?subject=help> List-Subscribe: <https://gcc.gnu.org/mailman/listinfo/gcc-patches>, <mailto:gcc-patches-request@gcc.gnu.org?subject=subscribe> From: Patrick Palka via Gcc-patches <gcc-patches@gcc.gnu.org> Reply-To: Patrick Palka <ppalka@redhat.com> Errors-To: gcc-patches-bounces+patchwork=sourceware.org@gcc.gnu.org Sender: "Gcc-patches" <gcc-patches-bounces+patchwork=sourceware.org@gcc.gnu.org> |
Series |
c++: alias template arguments are evaluated [PR101906]
|
|
Commit Message
Patrick Palka
March 22, 2022, 5:18 p.m. UTC
Here we're neglecting to clear cp_unevaluated_operand when substituting into the arguments of the alias template-id skip<(T(), 0), T> with T=A, which means cp_unevaluated_operand remains set during mark_used for A::A() and so we never synthesize it. Later constant evaluation for the substituted template argument (A(), 0) (during coerce_template_parms) fails with "'constexpr A::A()' used before its definition" since it was never synthesized. This minimal patch makes us clear cp_unevaluated_operand when substituting into the template arguments of an alias template-id, as in tsubst_aggr_type. (A few lines below we also substitute into the template arguments of a class-scope typedef, during which we arguably also should clear cp_unevaluated_operand, but I wasn't able to come up with a testcase for which this mattered, because if we've named a class-scope typedef, then at some point tsubst_aggr_type must have already substituted the class scope, which performs the same substitution with cp_unevaluated_operand cleared.) Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for trunk? PR c++/101906 gcc/cp/ChangeLog: * pt.cc (tsubst): Clear cp_unevaluated_operand when substituting the template arguments of an alias template specialization. gcc/testsuite/ChangeLog: * g++.dg/cpp0x/alias-decl-75.C: New test. * g++.dg/cpp0x/alias-decl-75a.C: New test. --- gcc/cp/pt.cc | 6 +++++- gcc/testsuite/g++.dg/cpp0x/alias-decl-75.C | 15 +++++++++++++++ gcc/testsuite/g++.dg/cpp0x/alias-decl-75a.C | 16 ++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 gcc/testsuite/g++.dg/cpp0x/alias-decl-75.C create mode 100644 gcc/testsuite/g++.dg/cpp0x/alias-decl-75a.C
Comments
On Tue, 22 Mar 2022, Patrick Palka wrote: > Here we're neglecting to clear cp_unevaluated_operand when substituting > into the arguments of the alias template-id skip<(T(), 0), T> with T=A, > which means cp_unevaluated_operand remains set during mark_used for > A::A() and so we never synthesize it. Later constant evaluation for > the substituted template argument (A(), 0) (during coerce_template_parms) > fails with "'constexpr A::A()' used before its definition" since it was > never synthesized. It occurred to me to check the case where 'skip' is a function/variable template instead of an alias template, and unfortunately seems we run into the same issue: template<int, class T> T skip(); // Function template // template<int, class T> T skip; // Variable template template<class T> constexpr unsigned sizeof_() { return sizeof(skip<(T(), 0), T>()); // return sizeof(skip<(T(), 0), T>); } struct A { int m = -1; }; static_assert(sizeof_<A>() == sizeof(A), ""); <stdin>: In instantiation of ‘constexpr unsigned int sizeof_() [with T = A]’: <stdin>:14:25: required from here <stdin>:6:34: error: ‘constexpr A::A()’ used before its definition We can fix this similarly by clearing cp_unevaluated_operand when substituting into the arguments of a TEMPLATE_ID_EXPR, but now I'm worried this cp_unevaluated_operand business might not be the best approach (despite it being consistent with what tsubst_aggr_type does). Maybe instantiate_cx_fn_r should be responsible for making sure A::A() gets synthesized? > > This minimal patch makes us clear cp_unevaluated_operand when > substituting into the template arguments of an alias template-id, as in > tsubst_aggr_type. > > (A few lines below we also substitute into the template arguments of a > class-scope typedef, during which we arguably also should clear > cp_unevaluated_operand, but I wasn't able to come up with a testcase for > which this mattered, because if we've named a class-scope typedef, then > at some point tsubst_aggr_type must have already substituted the class > scope, which performs the same substitution with cp_unevaluated_operand > cleared.) > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for > trunk? > > PR c++/101906 > > gcc/cp/ChangeLog: > > * pt.cc (tsubst): Clear cp_unevaluated_operand when substituting > the template arguments of an alias template specialization. > > gcc/testsuite/ChangeLog: > > * g++.dg/cpp0x/alias-decl-75.C: New test. > * g++.dg/cpp0x/alias-decl-75a.C: New test. > --- > gcc/cp/pt.cc | 6 +++++- > gcc/testsuite/g++.dg/cpp0x/alias-decl-75.C | 15 +++++++++++++++ > gcc/testsuite/g++.dg/cpp0x/alias-decl-75a.C | 16 ++++++++++++++++ > 3 files changed, 36 insertions(+), 1 deletion(-) > create mode 100644 gcc/testsuite/g++.dg/cpp0x/alias-decl-75.C > create mode 100644 gcc/testsuite/g++.dg/cpp0x/alias-decl-75a.C > > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc > index 7697615ac64..c7116849551 100644 > --- a/gcc/cp/pt.cc > +++ b/gcc/cp/pt.cc > @@ -15545,7 +15545,11 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) > /* DECL represents an alias template and we want to > instantiate it. */ > tree tmpl = most_general_template (DECL_TI_TEMPLATE (decl)); > - tree gen_args = tsubst (DECL_TI_ARGS (decl), args, complain, in_decl); > + tree gen_args; > + { > + cp_evaluated ev; > + gen_args = tsubst (DECL_TI_ARGS (decl), args, complain, in_decl); > + } > r = instantiate_alias_template (tmpl, gen_args, complain); > } > else if (DECL_CLASS_SCOPE_P (decl) > diff --git a/gcc/testsuite/g++.dg/cpp0x/alias-decl-75.C b/gcc/testsuite/g++.dg/cpp0x/alias-decl-75.C > new file mode 100644 > index 00000000000..c6176751283 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp0x/alias-decl-75.C > @@ -0,0 +1,15 @@ > +// PR c++/101906 > +// { dg-do compile { target c++11 } } > + > +template<int, class T> using skip = T; > + > +template<class T> > +constexpr unsigned sizeof_() { > + return sizeof(skip<(T(), 0), T>); > +} > + > +struct A { > + int m = -1; > +}; > + > +static_assert(sizeof_<A>() == sizeof(A), ""); > diff --git a/gcc/testsuite/g++.dg/cpp0x/alias-decl-75a.C b/gcc/testsuite/g++.dg/cpp0x/alias-decl-75a.C > new file mode 100644 > index 00000000000..ce08a84f6d9 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp0x/alias-decl-75a.C > @@ -0,0 +1,16 @@ > +// PR c++/101906 > +// Similar to alias-decl-75.C, but where the unevaluated context is a > +// constraint instead of sizeof. > +// { dg-do compile { target c++20 } } > + > +template<int> using voidify = void; > + > +template<class T> > +concept constant_value_initializable > + = requires { typename voidify<(T(), 0)>; }; > + > +struct A { > + int m = -1; > +}; > + > +static_assert(constant_value_initializable<A>); > -- > 2.35.1.607.gf01e51a7cf > >
On 3/22/22 14:31, Patrick Palka wrote: > On Tue, 22 Mar 2022, Patrick Palka wrote: > >> Here we're neglecting to clear cp_unevaluated_operand when substituting >> into the arguments of the alias template-id skip<(T(), 0), T> with T=A, >> which means cp_unevaluated_operand remains set during mark_used for >> A::A() and so we never synthesize it. Later constant evaluation for >> the substituted template argument (A(), 0) (during coerce_template_parms) >> fails with "'constexpr A::A()' used before its definition" since it was >> never synthesized. > > It occurred to me to check the case where 'skip' is a function/variable > template instead of an alias template, and unfortunately seems we run > into the same issue: > > template<int, class T> T skip(); // Function template > // template<int, class T> T skip; // Variable template > > template<class T> > constexpr unsigned sizeof_() { > return sizeof(skip<(T(), 0), T>()); > // return sizeof(skip<(T(), 0), T>); > } > > struct A { > int m = -1; > }; > > static_assert(sizeof_<A>() == sizeof(A), ""); > > <stdin>: In instantiation of ‘constexpr unsigned int sizeof_() [with T = A]’: > <stdin>:14:25: required from here > <stdin>:6:34: error: ‘constexpr A::A()’ used before its definition > > We can fix this similarly by clearing cp_unevaluated_operand when > substituting into the arguments of a TEMPLATE_ID_EXPR, but now I'm > worried this cp_unevaluated_operand business might not be the best > approach (despite it being consistent with what tsubst_aggr_type does). > > Maybe instantiate_cx_fn_r should be responsible for making sure A::A() > gets synthesized? Or cxx_eval_call_expression, but just as a workaround: manifestly-constant-evaluated expressions are evaluated even in an unevaluated operand, so I think adjusting cp_unevaluated_operand is correct. Perhaps tsubst_template_args should use cp_evaluated, and places that use plain tsubst for substituting template args should use it instead? Jason
On Wed, 23 Mar 2022, Jason Merrill wrote: > On 3/22/22 14:31, Patrick Palka wrote: > > On Tue, 22 Mar 2022, Patrick Palka wrote: > > > > > Here we're neglecting to clear cp_unevaluated_operand when substituting > > > into the arguments of the alias template-id skip<(T(), 0), T> with T=A, > > > which means cp_unevaluated_operand remains set during mark_used for > > > A::A() and so we never synthesize it. Later constant evaluation for > > > the substituted template argument (A(), 0) (during coerce_template_parms) > > > fails with "'constexpr A::A()' used before its definition" since it was > > > never synthesized. > > > > It occurred to me to check the case where 'skip' is a function/variable > > template instead of an alias template, and unfortunately seems we run > > into the same issue: > > > > template<int, class T> T skip(); // Function template > > // template<int, class T> T skip; // Variable template > > > > template<class T> > > constexpr unsigned sizeof_() { > > return sizeof(skip<(T(), 0), T>()); > > // return sizeof(skip<(T(), 0), T>); > > } > > > > struct A { > > int m = -1; > > }; > > > > static_assert(sizeof_<A>() == sizeof(A), ""); > > > > <stdin>: In instantiation of ‘constexpr unsigned int sizeof_() [with T = > > A]’: > > <stdin>:14:25: required from here > > <stdin>:6:34: error: ‘constexpr A::A()’ used before its definition > > > > We can fix this similarly by clearing cp_unevaluated_operand when > > substituting into the arguments of a TEMPLATE_ID_EXPR, but now I'm > > worried this cp_unevaluated_operand business might not be the best > > approach (despite it being consistent with what tsubst_aggr_type does). > > > > Maybe instantiate_cx_fn_r should be responsible for making sure A::A() > > gets synthesized? > > Or cxx_eval_call_expression, but just as a workaround: > manifestly-constant-evaluated expressions are evaluated even in an unevaluated > operand, so I think adjusting cp_unevaluated_operand is correct. > > Perhaps tsubst_template_args should use cp_evaluated, Makes sense. > and places that use plain tsubst for substituting template args should > use it instead? Even though tsubst already uses tsubst_template_args to substitute TREE_VEC? AFAICT this change would have no effect except when args is NULL_TREE, in which case tsubst exits early but tsubst_template_args doesn't. Here's what I have so far, which survives bootstrap and regtest. -- >8 -- Subject: [PATCH] c++: template-id arguments are evaluated [PR101906] Here we're neglecting to clear cp_unevaluated_operand when substituting into the arguments of the alias template-id skip<(T(), 0), T> with T=A, which means cp_unevaluated_operand remains set during mark_used for A::A() and so we never synthesize it. Later constant evaluation for the substituted template argument (A(), 0) (during coerce_template_parms) fails with "'constexpr A::A()' used before its definition" since it was never synthesized. This doesn't happen with a class template because tsubst_aggr_type clears cp_unevaluated_operand during substitution thereof. But since template arguments are generally manifestly constant-evaluated, which in turn are evaluated even in an unevaluated operand, we should be clearing cp_unevaluated_operand more broadly whenever substituting any set of template arguments. Thus this patch makes us clear cp_unevaluated_operand during tsubst_template_args. PR c++/101906 gcc/cp/ChangeLog: * pt.cc (tsubst_template_args): Set cp_evaluated here. (tsubst_aggr_type): Not here. gcc/testsuite/ChangeLog: * g++.dg/template/evaluated1.C: New test. * g++.dg/template/evaluated1a.C: New test. * g++.dg/template/evaluated1b.C: New test. * g++.dg/template/evaluated1c.C: New test. --- gcc/cp/pt.cc | 6 +++--- gcc/testsuite/g++.dg/template/evaluated1.C | 17 +++++++++++++++++ gcc/testsuite/g++.dg/template/evaluated1a.C | 16 ++++++++++++++++ gcc/testsuite/g++.dg/template/evaluated1b.C | 17 +++++++++++++++++ gcc/testsuite/g++.dg/template/evaluated1c.C | 17 +++++++++++++++++ 5 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 gcc/testsuite/g++.dg/template/evaluated1.C create mode 100644 gcc/testsuite/g++.dg/template/evaluated1a.C create mode 100644 gcc/testsuite/g++.dg/template/evaluated1b.C create mode 100644 gcc/testsuite/g++.dg/template/evaluated1c.C diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index ee7d2c935cc..7fe1c7653aa 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -13475,6 +13475,9 @@ tsubst_template_args (tree t, tree args, tsubst_flags_t complain, tree in_decl) if (t == error_mark_node) return error_mark_node; + /* In "sizeof(X<I>)" we need to evaluate "I". */ + cp_evaluated ev; + len = TREE_VEC_LENGTH (t); elts = XALLOCAVEC (tree, len); @@ -13709,9 +13712,6 @@ tsubst_aggr_type (tree t, tree context; tree r; - /* In "sizeof(X<I>)" we need to evaluate "I". */ - cp_evaluated ev; - /* First, determine the context for the type we are looking up. */ context = TYPE_CONTEXT (t); diff --git a/gcc/testsuite/g++.dg/template/evaluated1.C b/gcc/testsuite/g++.dg/template/evaluated1.C new file mode 100644 index 00000000000..41845c65acb --- /dev/null +++ b/gcc/testsuite/g++.dg/template/evaluated1.C @@ -0,0 +1,17 @@ +// PR c++/101906 +// Verify the template arguments of an alias template-id are evaluated even +// in an unevaluated context. +// { dg-do compile { target c++11 } } + +template<int, class T> using skip = T; + +template<class T> +constexpr unsigned sizeof_() { + return sizeof(skip<(T(), 0), T>); +} + +struct A { + int m = -1; +}; + +static_assert(sizeof_<A>() == sizeof(A), ""); diff --git a/gcc/testsuite/g++.dg/template/evaluated1a.C b/gcc/testsuite/g++.dg/template/evaluated1a.C new file mode 100644 index 00000000000..78286871004 --- /dev/null +++ b/gcc/testsuite/g++.dg/template/evaluated1a.C @@ -0,0 +1,16 @@ +// PR c++/101906 +// Like unevaluated1.C, but where the unevaluated context is a +// constraint instead of sizeof. +// { dg-do compile { target c++20 } } + +template<int> using voidify = void; + +template<class T> +concept constant_value_initializable + = requires { typename voidify<(T(), 0)>; }; + +struct A { + int m = -1; +}; + +static_assert(constant_value_initializable<A>); diff --git a/gcc/testsuite/g++.dg/template/evaluated1b.C b/gcc/testsuite/g++.dg/template/evaluated1b.C new file mode 100644 index 00000000000..7994065ac86 --- /dev/null +++ b/gcc/testsuite/g++.dg/template/evaluated1b.C @@ -0,0 +1,17 @@ +// PR c++/101906 +// Like unevaluated1.C, but using a function template instead of an +// alias template. +// { dg-do compile { target c++14 } } + +template<int, class T> T skip(); + +template<class T> +constexpr unsigned sizeof_() { + return sizeof(skip<(T(), 0), T>()); +} + +struct A { + int m = -1; +}; + +static_assert(sizeof_<A>() == sizeof(A), ""); diff --git a/gcc/testsuite/g++.dg/template/evaluated1c.C b/gcc/testsuite/g++.dg/template/evaluated1c.C new file mode 100644 index 00000000000..15c55821c01 --- /dev/null +++ b/gcc/testsuite/g++.dg/template/evaluated1c.C @@ -0,0 +1,17 @@ +// PR c++/101906 +// Like unevaluated1b.C, but using a variable template instead of a +// function template. +// { dg-do compile { target c++14 } } + +template<int, class T> T skip; + +template<class T> +constexpr unsigned sizeof_() { + return sizeof(skip<(T(), 0), T>); +} + +struct A { + int m = -1; +}; + +static_assert(sizeof_<A>() == sizeof(A), "");
On 6/7/22 14:25, Patrick Palka wrote: > On Wed, 23 Mar 2022, Jason Merrill wrote: > >> On 3/22/22 14:31, Patrick Palka wrote: >>> On Tue, 22 Mar 2022, Patrick Palka wrote: >>> >>>> Here we're neglecting to clear cp_unevaluated_operand when substituting >>>> into the arguments of the alias template-id skip<(T(), 0), T> with T=A, >>>> which means cp_unevaluated_operand remains set during mark_used for >>>> A::A() and so we never synthesize it. Later constant evaluation for >>>> the substituted template argument (A(), 0) (during coerce_template_parms) >>>> fails with "'constexpr A::A()' used before its definition" since it was >>>> never synthesized. >>> >>> It occurred to me to check the case where 'skip' is a function/variable >>> template instead of an alias template, and unfortunately seems we run >>> into the same issue: >>> >>> template<int, class T> T skip(); // Function template >>> // template<int, class T> T skip; // Variable template >>> >>> template<class T> >>> constexpr unsigned sizeof_() { >>> return sizeof(skip<(T(), 0), T>()); >>> // return sizeof(skip<(T(), 0), T>); >>> } >>> >>> struct A { >>> int m = -1; >>> }; >>> >>> static_assert(sizeof_<A>() == sizeof(A), ""); >>> >>> <stdin>: In instantiation of ‘constexpr unsigned int sizeof_() [with T = >>> A]’: >>> <stdin>:14:25: required from here >>> <stdin>:6:34: error: ‘constexpr A::A()’ used before its definition >>> >>> We can fix this similarly by clearing cp_unevaluated_operand when >>> substituting into the arguments of a TEMPLATE_ID_EXPR, but now I'm >>> worried this cp_unevaluated_operand business might not be the best >>> approach (despite it being consistent with what tsubst_aggr_type does). >>> >>> Maybe instantiate_cx_fn_r should be responsible for making sure A::A() >>> gets synthesized? >> >> Or cxx_eval_call_expression, but just as a workaround: >> manifestly-constant-evaluated expressions are evaluated even in an unevaluated >> operand, so I think adjusting cp_unevaluated_operand is correct. >> >> Perhaps tsubst_template_args should use cp_evaluated, > > Makes sense. > >> and places that use plain tsubst for substituting template args should >> use it instead? > > Even though tsubst already uses tsubst_template_args to substitute > TREE_VEC? AFAICT this change would have no effect except when > args is NULL_TREE, in which case tsubst exits early but > tsubst_template_args doesn't. Never mind, then. > Here's what I have so far, which survives bootstrap and regtest. OK. > -- >8 -- > > Subject: [PATCH] c++: template-id arguments are evaluated [PR101906] > > Here we're neglecting to clear cp_unevaluated_operand when substituting > into the arguments of the alias template-id skip<(T(), 0), T> with T=A, > which means cp_unevaluated_operand remains set during mark_used for > A::A() and so we never synthesize it. Later constant evaluation for > the substituted template argument (A(), 0) (during coerce_template_parms) > fails with "'constexpr A::A()' used before its definition" since it was > never synthesized. > > This doesn't happen with a class template because tsubst_aggr_type > clears cp_unevaluated_operand during substitution thereof. But > since template arguments are generally manifestly constant-evaluated, > which in turn are evaluated even in an unevaluated operand, we > should be clearing cp_unevaluated_operand more broadly whenever > substituting any set of template arguments. Thus this patch makes us > clear cp_unevaluated_operand during tsubst_template_args. > > PR c++/101906 > > gcc/cp/ChangeLog: > > * pt.cc (tsubst_template_args): Set cp_evaluated here. > (tsubst_aggr_type): Not here. > > gcc/testsuite/ChangeLog: > > * g++.dg/template/evaluated1.C: New test. > * g++.dg/template/evaluated1a.C: New test. > * g++.dg/template/evaluated1b.C: New test. > * g++.dg/template/evaluated1c.C: New test. > --- > gcc/cp/pt.cc | 6 +++--- > gcc/testsuite/g++.dg/template/evaluated1.C | 17 +++++++++++++++++ > gcc/testsuite/g++.dg/template/evaluated1a.C | 16 ++++++++++++++++ > gcc/testsuite/g++.dg/template/evaluated1b.C | 17 +++++++++++++++++ > gcc/testsuite/g++.dg/template/evaluated1c.C | 17 +++++++++++++++++ > 5 files changed, 70 insertions(+), 3 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/template/evaluated1.C > create mode 100644 gcc/testsuite/g++.dg/template/evaluated1a.C > create mode 100644 gcc/testsuite/g++.dg/template/evaluated1b.C > create mode 100644 gcc/testsuite/g++.dg/template/evaluated1c.C > > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc > index ee7d2c935cc..7fe1c7653aa 100644 > --- a/gcc/cp/pt.cc > +++ b/gcc/cp/pt.cc > @@ -13475,6 +13475,9 @@ tsubst_template_args (tree t, tree args, tsubst_flags_t complain, tree in_decl) > if (t == error_mark_node) > return error_mark_node; > > + /* In "sizeof(X<I>)" we need to evaluate "I". */ > + cp_evaluated ev; > + > len = TREE_VEC_LENGTH (t); > elts = XALLOCAVEC (tree, len); > > @@ -13709,9 +13712,6 @@ tsubst_aggr_type (tree t, > tree context; > tree r; > > - /* In "sizeof(X<I>)" we need to evaluate "I". */ > - cp_evaluated ev; > - > /* First, determine the context for the type we are looking > up. */ > context = TYPE_CONTEXT (t); > diff --git a/gcc/testsuite/g++.dg/template/evaluated1.C b/gcc/testsuite/g++.dg/template/evaluated1.C > new file mode 100644 > index 00000000000..41845c65acb > --- /dev/null > +++ b/gcc/testsuite/g++.dg/template/evaluated1.C > @@ -0,0 +1,17 @@ > +// PR c++/101906 > +// Verify the template arguments of an alias template-id are evaluated even > +// in an unevaluated context. > +// { dg-do compile { target c++11 } } > + > +template<int, class T> using skip = T; > + > +template<class T> > +constexpr unsigned sizeof_() { > + return sizeof(skip<(T(), 0), T>); > +} > + > +struct A { > + int m = -1; > +}; > + > +static_assert(sizeof_<A>() == sizeof(A), ""); > diff --git a/gcc/testsuite/g++.dg/template/evaluated1a.C b/gcc/testsuite/g++.dg/template/evaluated1a.C > new file mode 100644 > index 00000000000..78286871004 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/template/evaluated1a.C > @@ -0,0 +1,16 @@ > +// PR c++/101906 > +// Like unevaluated1.C, but where the unevaluated context is a > +// constraint instead of sizeof. > +// { dg-do compile { target c++20 } } > + > +template<int> using voidify = void; > + > +template<class T> > +concept constant_value_initializable > + = requires { typename voidify<(T(), 0)>; }; > + > +struct A { > + int m = -1; > +}; > + > +static_assert(constant_value_initializable<A>); > diff --git a/gcc/testsuite/g++.dg/template/evaluated1b.C b/gcc/testsuite/g++.dg/template/evaluated1b.C > new file mode 100644 > index 00000000000..7994065ac86 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/template/evaluated1b.C > @@ -0,0 +1,17 @@ > +// PR c++/101906 > +// Like unevaluated1.C, but using a function template instead of an > +// alias template. > +// { dg-do compile { target c++14 } } > + > +template<int, class T> T skip(); > + > +template<class T> > +constexpr unsigned sizeof_() { > + return sizeof(skip<(T(), 0), T>()); > +} > + > +struct A { > + int m = -1; > +}; > + > +static_assert(sizeof_<A>() == sizeof(A), ""); > diff --git a/gcc/testsuite/g++.dg/template/evaluated1c.C b/gcc/testsuite/g++.dg/template/evaluated1c.C > new file mode 100644 > index 00000000000..15c55821c01 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/template/evaluated1c.C > @@ -0,0 +1,17 @@ > +// PR c++/101906 > +// Like unevaluated1b.C, but using a variable template instead of a > +// function template. > +// { dg-do compile { target c++14 } } > + > +template<int, class T> T skip; > + > +template<class T> > +constexpr unsigned sizeof_() { > + return sizeof(skip<(T(), 0), T>); > +} > + > +struct A { > + int m = -1; > +}; > + > +static_assert(sizeof_<A>() == sizeof(A), "");
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index 7697615ac64..c7116849551 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -15545,7 +15545,11 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) /* DECL represents an alias template and we want to instantiate it. */ tree tmpl = most_general_template (DECL_TI_TEMPLATE (decl)); - tree gen_args = tsubst (DECL_TI_ARGS (decl), args, complain, in_decl); + tree gen_args; + { + cp_evaluated ev; + gen_args = tsubst (DECL_TI_ARGS (decl), args, complain, in_decl); + } r = instantiate_alias_template (tmpl, gen_args, complain); } else if (DECL_CLASS_SCOPE_P (decl) diff --git a/gcc/testsuite/g++.dg/cpp0x/alias-decl-75.C b/gcc/testsuite/g++.dg/cpp0x/alias-decl-75.C new file mode 100644 index 00000000000..c6176751283 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/alias-decl-75.C @@ -0,0 +1,15 @@ +// PR c++/101906 +// { dg-do compile { target c++11 } } + +template<int, class T> using skip = T; + +template<class T> +constexpr unsigned sizeof_() { + return sizeof(skip<(T(), 0), T>); +} + +struct A { + int m = -1; +}; + +static_assert(sizeof_<A>() == sizeof(A), ""); diff --git a/gcc/testsuite/g++.dg/cpp0x/alias-decl-75a.C b/gcc/testsuite/g++.dg/cpp0x/alias-decl-75a.C new file mode 100644 index 00000000000..ce08a84f6d9 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/alias-decl-75a.C @@ -0,0 +1,16 @@ +// PR c++/101906 +// Similar to alias-decl-75.C, but where the unevaluated context is a +// constraint instead of sizeof. +// { dg-do compile { target c++20 } } + +template<int> using voidify = void; + +template<class T> +concept constant_value_initializable + = requires { typename voidify<(T(), 0)>; }; + +struct A { + int m = -1; +}; + +static_assert(constant_value_initializable<A>);