c++: unnecessary instantiation of constexpr var [PR99130]

Message ID 20220907194100.879066-1-ppalka@redhat.com
State New
Headers
Series c++: unnecessary instantiation of constexpr var [PR99130] |

Commit Message

Patrick Palka Sept. 7, 2022, 7:41 p.m. UTC
  Here the use of the constexpr member/variable specialization 'value'
from within an unevaluated context causes us to overeagerly instantiate
it, via maybe_instantiate_decl called from mark_used, despite only its
declaration not its definition being needed.

We used to have the same issue for constexpr function specializations
until r6-1309-g81371eff9bc7ef made us delay their instantiation until
necessary during constexpr evaluation.

So this patch makes us avoid unnecessarily instantiating constexpr
variable template specializations from mark_used as well.  To that end
this patch pulls out the test in maybe_instantiate_decl

  (decl_maybe_constant_var_p (decl)
   || (TREE_CODE (decl) == FUNCTION_DECL
       && DECL_OMP_DECLARE_REDUCTION_P (decl))
   || undeduced_auto_decl (decl))

into each of its three callers (including mark_used) and refines the
test appropriately.  The net result is that only mark_used is changed,
because the other two callers, resolve_address_of_overloaded_function
and decl_constant_var_p, already guard the call appropriately.  And
presumably decl_constant_var_p will take care of instantiation when
needed for e.g. constexpr evaluation.

Bootstrapped and regteste on x86_64-pc-linux-gnu, does this look OK for
trunk?

	PR c++/99130

gcc/cp/ChangeLog:

	* decl2.cc (maybe_instantiate_decl): Adjust function comment.
	Check VAR_OR_FUNCTION_DECL_P. Pull out the disjunction into ...
	(mark_used): ... here, removing the decl_maybe_constant_var_p
	part of it.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp1y/var-templ70.C: New test.
---
 gcc/cp/decl2.cc                          | 33 ++++++++----------------
 gcc/testsuite/g++.dg/cpp1y/var-templ70.C | 19 ++++++++++++++
 2 files changed, 30 insertions(+), 22 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/var-templ70.C
  

Comments

Jason Merrill Sept. 7, 2022, 7:55 p.m. UTC | #1
On 9/7/22 15:41, Patrick Palka wrote:
> Here the use of the constexpr member/variable specialization 'value'
> from within an unevaluated context causes us to overeagerly instantiate
> it, via maybe_instantiate_decl called from mark_used, despite only its
> declaration not its definition being needed.

If the issue is with unevaluated context, maybe maybe_instantiate_decl 
should guard the call to decl_maybe_constant_var_p with 
!cp_unevaluated_operand?

> We used to have the same issue for constexpr function specializations
> until r6-1309-g81371eff9bc7ef made us delay their instantiation until
> necessary during constexpr evaluation.
> 
> So this patch makes us avoid unnecessarily instantiating constexpr
> variable template specializations from mark_used as well.  To that end
> this patch pulls out the test in maybe_instantiate_decl
> 
>    (decl_maybe_constant_var_p (decl)
>     || (TREE_CODE (decl) == FUNCTION_DECL
>         && DECL_OMP_DECLARE_REDUCTION_P (decl))
>     || undeduced_auto_decl (decl))
> 
> into each of its three callers (including mark_used) and refines the
> test appropriately.  The net result is that only mark_used is changed,
> because the other two callers, resolve_address_of_overloaded_function
> and decl_constant_var_p, already guard the call appropriately.  And
> presumably decl_constant_var_p will take care of instantiation when
> needed for e.g. constexpr evaluation.
> 
> Bootstrapped and regteste on x86_64-pc-linux-gnu, does this look OK for
> trunk?
> 
> 	PR c++/99130
> 
> gcc/cp/ChangeLog:
> 
> 	* decl2.cc (maybe_instantiate_decl): Adjust function comment.
> 	Check VAR_OR_FUNCTION_DECL_P. Pull out the disjunction into ...
> 	(mark_used): ... here, removing the decl_maybe_constant_var_p
> 	part of it.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp1y/var-templ70.C: New test.
> ---
>   gcc/cp/decl2.cc                          | 33 ++++++++----------------
>   gcc/testsuite/g++.dg/cpp1y/var-templ70.C | 19 ++++++++++++++
>   2 files changed, 30 insertions(+), 22 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp1y/var-templ70.C
> 
> diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
> index 89ab2545d64..cd188813bee 100644
> --- a/gcc/cp/decl2.cc
> +++ b/gcc/cp/decl2.cc
> @@ -5381,24 +5381,15 @@ possibly_inlined_p (tree decl)
>     return true;
>   }
>   
> -/* Normally, we can wait until instantiation-time to synthesize DECL.
> -   However, if DECL is a static data member initialized with a constant
> -   or a constexpr function, we need it right now because a reference to
> -   such a data member or a call to such function is not value-dependent.
> -   For a function that uses auto in the return type, we need to instantiate
> -   it to find out its type.  For OpenMP user defined reductions, we need
> -   them instantiated for reduction clauses which inline them by hand
> -   directly.  */
> +/* If DECL is a function or variable template specialization, instantiate
> +   its definition now.  */
>   
>   void
>   maybe_instantiate_decl (tree decl)
>   {
> -  if (DECL_LANG_SPECIFIC (decl)
> +  if (VAR_OR_FUNCTION_DECL_P (decl)
> +      && DECL_LANG_SPECIFIC (decl)
>         && DECL_TEMPLATE_INFO (decl)
> -      && (decl_maybe_constant_var_p (decl)
> -	  || (TREE_CODE (decl) == FUNCTION_DECL
> -	      && DECL_OMP_DECLARE_REDUCTION_P (decl))
> -	  || undeduced_auto_decl (decl))
>         && !DECL_DECLARED_CONCEPT_P (decl)
>         && !uses_template_parms (DECL_TI_ARGS (decl)))
>       {
> @@ -5700,15 +5691,13 @@ mark_used (tree decl, tsubst_flags_t complain)
>         return false;
>       }
>   
> -  /* Normally, we can wait until instantiation-time to synthesize DECL.
> -     However, if DECL is a static data member initialized with a constant
> -     or a constexpr function, we need it right now because a reference to
> -     such a data member or a call to such function is not value-dependent.
> -     For a function that uses auto in the return type, we need to instantiate
> -     it to find out its type.  For OpenMP user defined reductions, we need
> -     them instantiated for reduction clauses which inline them by hand
> -     directly.  */
> -  maybe_instantiate_decl (decl);
> +  /* If DECL has a deduced return type, we need to instantiate it now to
> +     find out its type.  For OpenMP user defined reductions, we need them
> +     instantiated for reduction clauses which inline them by hand directly.  */
> +  if (undeduced_auto_decl (decl)
> +      || (TREE_CODE (decl) == FUNCTION_DECL
> +	  && DECL_OMP_DECLARE_REDUCTION_P (decl)))
> +    maybe_instantiate_decl (decl);
>   
>     if (processing_template_decl || in_template_function ())
>       return true;
> diff --git a/gcc/testsuite/g++.dg/cpp1y/var-templ70.C b/gcc/testsuite/g++.dg/cpp1y/var-templ70.C
> new file mode 100644
> index 00000000000..80965657c32
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp1y/var-templ70.C
> @@ -0,0 +1,19 @@
> +// PR c++/99130
> +// { dg-do compile { target c++14 } }
> +
> +template<class T>
> +struct A {
> +  static constexpr int value = T::value;
> +};
> +
> +struct B {
> +  template<class T>
> +  static constexpr int value = T::value;
> +};
> +
> +template<class T>
> +constexpr int value = T::value;
> +
> +using ty1 = decltype(A<int>::value);
> +using ty2 = decltype(B::value<int>);
> +using ty3 = decltype(value<int>);
  
Patrick Palka Sept. 7, 2022, 8:40 p.m. UTC | #2
On Wed, 7 Sep 2022, Jason Merrill wrote:

> On 9/7/22 15:41, Patrick Palka wrote:
> > Here the use of the constexpr member/variable specialization 'value'
> > from within an unevaluated context causes us to overeagerly instantiate
> > it, via maybe_instantiate_decl called from mark_used, despite only its
> > declaration not its definition being needed.
> 
> If the issue is with unevaluated context, maybe maybe_instantiate_decl should
> guard the call to decl_maybe_constant_var_p with !cp_unevaluated_operand?

Hmm, that seems to work too.  But IIUC this would mean in an evaluated
(but non-constexpr) context we'd continue to instantiate constexpr
variables _immediately_ rather than ideally allowing mark_used to
postpone their instantiation until the end of TU processing (which is
what happens with the below approach).

Another benefit of the below approach is that from within a template
definition we we now avoid instantiation altogether e.g. for

  template<class T> constexpr int value = /* blah */;

  template<class T>
  int f() { return value<int>; }

we no longer instantiate value<int> which IIUC is consistent with how we
handle other kinds of specializations used within a template definition.
So making mark_used no longer instantiate constexpr variables immediately
(in both evaluated and unevaluated contexts) seems to yield the most
benefits.

> 
> > We used to have the same issue for constexpr function specializations
> > until r6-1309-g81371eff9bc7ef made us delay their instantiation until
> > necessary during constexpr evaluation.
> > 
> > So this patch makes us avoid unnecessarily instantiating constexpr
> > variable template specializations from mark_used as well.  To that end
> > this patch pulls out the test in maybe_instantiate_decl
> > 
> >    (decl_maybe_constant_var_p (decl)
> >     || (TREE_CODE (decl) == FUNCTION_DECL
> >         && DECL_OMP_DECLARE_REDUCTION_P (decl))
> >     || undeduced_auto_decl (decl))
> > 
> > into each of its three callers (including mark_used) and refines the
> > test appropriately.  The net result is that only mark_used is changed,
> > because the other two callers, resolve_address_of_overloaded_function
> > and decl_constant_var_p, already guard the call appropriately.  And
> > presumably decl_constant_var_p will take care of instantiation when
> > needed for e.g. constexpr evaluation.
> > 
> > Bootstrapped and regteste on x86_64-pc-linux-gnu, does this look OK for
> > trunk?
> > 
> > 	PR c++/99130
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* decl2.cc (maybe_instantiate_decl): Adjust function comment.
> > 	Check VAR_OR_FUNCTION_DECL_P. Pull out the disjunction into ...
> > 	(mark_used): ... here, removing the decl_maybe_constant_var_p
> > 	part of it.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/cpp1y/var-templ70.C: New test.
> > ---
> >   gcc/cp/decl2.cc                          | 33 ++++++++----------------
> >   gcc/testsuite/g++.dg/cpp1y/var-templ70.C | 19 ++++++++++++++
> >   2 files changed, 30 insertions(+), 22 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/cpp1y/var-templ70.C
> > 
> > diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
> > index 89ab2545d64..cd188813bee 100644
> > --- a/gcc/cp/decl2.cc
> > +++ b/gcc/cp/decl2.cc
> > @@ -5381,24 +5381,15 @@ possibly_inlined_p (tree decl)
> >     return true;
> >   }
> >   -/* Normally, we can wait until instantiation-time to synthesize DECL.
> > -   However, if DECL is a static data member initialized with a constant
> > -   or a constexpr function, we need it right now because a reference to
> > -   such a data member or a call to such function is not value-dependent.
> > -   For a function that uses auto in the return type, we need to instantiate
> > -   it to find out its type.  For OpenMP user defined reductions, we need
> > -   them instantiated for reduction clauses which inline them by hand
> > -   directly.  */
> > +/* If DECL is a function or variable template specialization, instantiate
> > +   its definition now.  */
> >     void
> >   maybe_instantiate_decl (tree decl)
> >   {
> > -  if (DECL_LANG_SPECIFIC (decl)
> > +  if (VAR_OR_FUNCTION_DECL_P (decl)
> > +      && DECL_LANG_SPECIFIC (decl)
> >         && DECL_TEMPLATE_INFO (decl)
> > -      && (decl_maybe_constant_var_p (decl)
> > -	  || (TREE_CODE (decl) == FUNCTION_DECL
> > -	      && DECL_OMP_DECLARE_REDUCTION_P (decl))
> > -	  || undeduced_auto_decl (decl))
> >         && !DECL_DECLARED_CONCEPT_P (decl)
> >         && !uses_template_parms (DECL_TI_ARGS (decl)))
> >       {
> > @@ -5700,15 +5691,13 @@ mark_used (tree decl, tsubst_flags_t complain)
> >         return false;
> >       }
> >   -  /* Normally, we can wait until instantiation-time to synthesize DECL.
> > -     However, if DECL is a static data member initialized with a constant
> > -     or a constexpr function, we need it right now because a reference to
> > -     such a data member or a call to such function is not value-dependent.
> > -     For a function that uses auto in the return type, we need to
> > instantiate
> > -     it to find out its type.  For OpenMP user defined reductions, we need
> > -     them instantiated for reduction clauses which inline them by hand
> > -     directly.  */
> > -  maybe_instantiate_decl (decl);
> > +  /* If DECL has a deduced return type, we need to instantiate it now to
> > +     find out its type.  For OpenMP user defined reductions, we need them
> > +     instantiated for reduction clauses which inline them by hand directly.
> > */
> > +  if (undeduced_auto_decl (decl)
> > +      || (TREE_CODE (decl) == FUNCTION_DECL
> > +	  && DECL_OMP_DECLARE_REDUCTION_P (decl)))
> > +    maybe_instantiate_decl (decl);
> >       if (processing_template_decl || in_template_function ())
> >       return true;
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/var-templ70.C
> > b/gcc/testsuite/g++.dg/cpp1y/var-templ70.C
> > new file mode 100644
> > index 00000000000..80965657c32
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp1y/var-templ70.C
> > @@ -0,0 +1,19 @@
> > +// PR c++/99130
> > +// { dg-do compile { target c++14 } }
> > +
> > +template<class T>
> > +struct A {
> > +  static constexpr int value = T::value;
> > +};
> > +
> > +struct B {
> > +  template<class T>
> > +  static constexpr int value = T::value;
> > +};
> > +
> > +template<class T>
> > +constexpr int value = T::value;
> > +
> > +using ty1 = decltype(A<int>::value);
> > +using ty2 = decltype(B::value<int>);
> > +using ty3 = decltype(value<int>);
> 
>
  
Jason Merrill Sept. 8, 2022, 12:46 p.m. UTC | #3
On 9/7/22 16:40, Patrick Palka wrote:
> On Wed, 7 Sep 2022, Jason Merrill wrote:
> 
>> On 9/7/22 15:41, Patrick Palka wrote:
>>> Here the use of the constexpr member/variable specialization 'value'
>>> from within an unevaluated context causes us to overeagerly instantiate
>>> it, via maybe_instantiate_decl called from mark_used, despite only its
>>> declaration not its definition being needed.
>>
>> If the issue is with unevaluated context, maybe maybe_instantiate_decl should
>> guard the call to decl_maybe_constant_var_p with !cp_unevaluated_operand?
> 
> Hmm, that seems to work too.  But IIUC this would mean in an evaluated
> (but non-constexpr) context we'd continue to instantiate constexpr
> variables _immediately_ rather than ideally allowing mark_used to
> postpone their instantiation until the end of TU processing (which is
> what happens with the below approach).
> 
> Another benefit of the below approach is that from within a template
> definition we we now avoid instantiation altogether e.g. for
> 
>    template<class T> constexpr int value = /* blah */;
> 
>    template<class T>
>    int f() { return value<int>; }
> 
> we no longer instantiate value<int> which IIUC is consistent with how we
> handle other kinds of specializations used within a template definition.
> So making mark_used no longer instantiate constexpr variables immediately
> (in both evaluated and unevaluated contexts) seems to yield the most
> benefits.

Makes sense.  The patch is OK.

>>> We used to have the same issue for constexpr function specializations
>>> until r6-1309-g81371eff9bc7ef made us delay their instantiation until
>>> necessary during constexpr evaluation.
>>>
>>> So this patch makes us avoid unnecessarily instantiating constexpr
>>> variable template specializations from mark_used as well.  To that end
>>> this patch pulls out the test in maybe_instantiate_decl
>>>
>>>     (decl_maybe_constant_var_p (decl)
>>>      || (TREE_CODE (decl) == FUNCTION_DECL
>>>          && DECL_OMP_DECLARE_REDUCTION_P (decl))
>>>      || undeduced_auto_decl (decl))
>>>
>>> into each of its three callers (including mark_used) and refines the
>>> test appropriately.  The net result is that only mark_used is changed,
>>> because the other two callers, resolve_address_of_overloaded_function
>>> and decl_constant_var_p, already guard the call appropriately.  And
>>> presumably decl_constant_var_p will take care of instantiation when
>>> needed for e.g. constexpr evaluation.
>>>
>>> Bootstrapped and regteste on x86_64-pc-linux-gnu, does this look OK for
>>> trunk?
>>>
>>> 	PR c++/99130
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> 	* decl2.cc (maybe_instantiate_decl): Adjust function comment. >>> 	Check VAR_OR_FUNCTION_DECL_P. Pull out the disjunction into ...
>>> 	(mark_used): ... here, removing the decl_maybe_constant_var_p
>>> 	part of it.
>>> gcc/testsuite/ChangeLog:
>>>
>>> 	* g++.dg/cpp1y/var-templ70.C: New test.
>>> ---
>>>    gcc/cp/decl2.cc                          | 33 ++++++++----------------
>>>    gcc/testsuite/g++.dg/cpp1y/var-templ70.C | 19 ++++++++++++++
>>>    2 files changed, 30 insertions(+), 22 deletions(-)
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp1y/var-templ70.C
>>>
>>> diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
>>> index 89ab2545d64..cd188813bee 100644
>>> --- a/gcc/cp/decl2.cc
>>> +++ b/gcc/cp/decl2.cc
>>> @@ -5381,24 +5381,15 @@ possibly_inlined_p (tree decl)
>>>      return true;
>>>    }
>>>    -/* Normally, we can wait until instantiation-time to synthesize DECL.
>>> -   However, if DECL is a static data member initialized with a constant
>>> -   or a constexpr function, we need it right now because a reference to
>>> -   such a data member or a call to such function is not value-dependent.
>>> -   For a function that uses auto in the return type, we need to instantiate
>>> -   it to find out its type.  For OpenMP user defined reductions, we need
>>> -   them instantiated for reduction clauses which inline them by hand
>>> -   directly.  */
>>> +/* If DECL is a function or variable template specialization, instantiate
>>> +   its definition now.  */
>>>      void
>>>    maybe_instantiate_decl (tree decl)
>>>    {
>>> -  if (DECL_LANG_SPECIFIC (decl)
>>> +  if (VAR_OR_FUNCTION_DECL_P (decl)
>>> +      && DECL_LANG_SPECIFIC (decl)
>>>          && DECL_TEMPLATE_INFO (decl)
>>> -      && (decl_maybe_constant_var_p (decl)
>>> -	  || (TREE_CODE (decl) == FUNCTION_DECL
>>> -	      && DECL_OMP_DECLARE_REDUCTION_P (decl))
>>> -	  || undeduced_auto_decl (decl))
>>>          && !DECL_DECLARED_CONCEPT_P (decl)
>>>          && !uses_template_parms (DECL_TI_ARGS (decl)))
>>>        {
>>> @@ -5700,15 +5691,13 @@ mark_used (tree decl, tsubst_flags_t complain)
>>>          return false;
>>>        }
>>>    -  /* Normally, we can wait until instantiation-time to synthesize DECL.
>>> -     However, if DECL is a static data member initialized with a constant
>>> -     or a constexpr function, we need it right now because a reference to
>>> -     such a data member or a call to such function is not value-dependent.
>>> -     For a function that uses auto in the return type, we need to
>>> instantiate
>>> -     it to find out its type.  For OpenMP user defined reductions, we need
>>> -     them instantiated for reduction clauses which inline them by hand
>>> -     directly.  */
>>> -  maybe_instantiate_decl (decl);
>>> +  /* If DECL has a deduced return type, we need to instantiate it now to
>>> +     find out its type.  For OpenMP user defined reductions, we need them
>>> +     instantiated for reduction clauses which inline them by hand directly.
>>> */
>>> +  if (undeduced_auto_decl (decl)
>>> +      || (TREE_CODE (decl) == FUNCTION_DECL
>>> +	  && DECL_OMP_DECLARE_REDUCTION_P (decl)))
>>> +    maybe_instantiate_decl (decl);
>>>        if (processing_template_decl || in_template_function ())
>>>        return true;
>>> diff --git a/gcc/testsuite/g++.dg/cpp1y/var-templ70.C
>>> b/gcc/testsuite/g++.dg/cpp1y/var-templ70.C
>>> new file mode 100644
>>> index 00000000000..80965657c32
>>> --- /dev/null
>>> +++ b/gcc/testsuite/g++.dg/cpp1y/var-templ70.C
>>> @@ -0,0 +1,19 @@
>>> +// PR c++/99130
>>> +// { dg-do compile { target c++14 } }
>>> +
>>> +template<class T>
>>> +struct A {
>>> +  static constexpr int value = T::value;
>>> +};
>>> +
>>> +struct B {
>>> +  template<class T>
>>> +  static constexpr int value = T::value;
>>> +};
>>> +
>>> +template<class T>
>>> +constexpr int value = T::value;
>>> +
>>> +using ty1 = decltype(A<int>::value);
>>> +using ty2 = decltype(B::value<int>);
>>> +using ty3 = decltype(value<int>);
>>
>>
>
  

Patch

diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index 89ab2545d64..cd188813bee 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -5381,24 +5381,15 @@  possibly_inlined_p (tree decl)
   return true;
 }
 
-/* Normally, we can wait until instantiation-time to synthesize DECL.
-   However, if DECL is a static data member initialized with a constant
-   or a constexpr function, we need it right now because a reference to
-   such a data member or a call to such function is not value-dependent.
-   For a function that uses auto in the return type, we need to instantiate
-   it to find out its type.  For OpenMP user defined reductions, we need
-   them instantiated for reduction clauses which inline them by hand
-   directly.  */
+/* If DECL is a function or variable template specialization, instantiate
+   its definition now.  */
 
 void
 maybe_instantiate_decl (tree decl)
 {
-  if (DECL_LANG_SPECIFIC (decl)
+  if (VAR_OR_FUNCTION_DECL_P (decl)
+      && DECL_LANG_SPECIFIC (decl)
       && DECL_TEMPLATE_INFO (decl)
-      && (decl_maybe_constant_var_p (decl)
-	  || (TREE_CODE (decl) == FUNCTION_DECL
-	      && DECL_OMP_DECLARE_REDUCTION_P (decl))
-	  || undeduced_auto_decl (decl))
       && !DECL_DECLARED_CONCEPT_P (decl)
       && !uses_template_parms (DECL_TI_ARGS (decl)))
     {
@@ -5700,15 +5691,13 @@  mark_used (tree decl, tsubst_flags_t complain)
       return false;
     }
 
-  /* Normally, we can wait until instantiation-time to synthesize DECL.
-     However, if DECL is a static data member initialized with a constant
-     or a constexpr function, we need it right now because a reference to
-     such a data member or a call to such function is not value-dependent.
-     For a function that uses auto in the return type, we need to instantiate
-     it to find out its type.  For OpenMP user defined reductions, we need
-     them instantiated for reduction clauses which inline them by hand
-     directly.  */
-  maybe_instantiate_decl (decl);
+  /* If DECL has a deduced return type, we need to instantiate it now to
+     find out its type.  For OpenMP user defined reductions, we need them
+     instantiated for reduction clauses which inline them by hand directly.  */
+  if (undeduced_auto_decl (decl)
+      || (TREE_CODE (decl) == FUNCTION_DECL
+	  && DECL_OMP_DECLARE_REDUCTION_P (decl)))
+    maybe_instantiate_decl (decl);
 
   if (processing_template_decl || in_template_function ())
     return true;
diff --git a/gcc/testsuite/g++.dg/cpp1y/var-templ70.C b/gcc/testsuite/g++.dg/cpp1y/var-templ70.C
new file mode 100644
index 00000000000..80965657c32
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/var-templ70.C
@@ -0,0 +1,19 @@ 
+// PR c++/99130
+// { dg-do compile { target c++14 } }
+
+template<class T>
+struct A {
+  static constexpr int value = T::value;
+};
+
+struct B {
+  template<class T>
+  static constexpr int value = T::value;
+};
+
+template<class T>
+constexpr int value = T::value;
+
+using ty1 = decltype(A<int>::value);
+using ty2 = decltype(B::value<int>);
+using ty3 = decltype(value<int>);