diff mbox series

c++: Allow constexpr references to non-static vars [PR100976]

Message ID 20210715211459.966387-1-polacek@redhat.com
State New
Headers show
Series c++: Allow constexpr references to non-static vars [PR100976] | expand

Commit Message

Marek Polacek July 15, 2021, 9:14 p.m. UTC
The combination of DR 2481 and DR 2126 should allow us to do

  void f()
  {
    constexpr const int &r = 42;
    static_assert(r == 42);
  }

because [expr.const]/4.7 now says that "a temporary object of
non-volatile const-qualified literal type whose lifetime is extended to
that of a variable that is usable in constant expressions" is usable in
a constant expression.

I think the temporary is supposed to be const-qualified, because Core 2481
says so.  I was happy to find out that we already mark the temporary as
const + constexpr in set_up_extended_ref_temp.

But that wasn't enough to make the test above work: references are
traditionally implemented as pointers, so the temporary object will be
(const int &)&D.1234, and verify_constant -> reduced_constant_expression_p
-> initializer_constant_valid_p_1 doesn't think that's OK -- and rightly
so -- the address of a local variable certainly isn't constant.  Therefore
I'm skipping the verify_constant check in cxx_eval_outermost_constant_expr.
(DECL_INITIAL isn't checked because maybe we are still waiting for
initialize_local_var to set it.)

Then we need to be able to evaluate such a reference.  This I do by
seeing through the reference in cxx_eval_constant_expression.  I can't
rely on decl_constant_value to pull out DECL_INITIAL, because the VAR_DECL
isn't DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P, and I think we don't
need to mess with that if we're keeping this purely in constexpr.

I wonder if we should accept

  void f2()
  {
    constexpr int &&r = 42;
    static_assert(r == 42);
  }

Currently we don't -- CP_TYPE_CONST_NON_VOLATILE_P (type) is false in
set_up_extended_ref_temp.

Does this make sense?  Bootstrapped/regtested on x86_64-pc-linux-gnu.

	PR c++/100976
	DR 2481

gcc/cp/ChangeLog:

	* constexpr.c (cxx_eval_constant_expression): For a constexpr
	reference, return its DECL_INITIAL.
	(cxx_eval_outermost_constant_expr): Don't verify the initializer
	for a constexpr variable of reference type.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp0x/constexpr-ref2.C: Remove dg-error.
	* g++.dg/cpp0x/constexpr-temp2.C: New test.
	* g++.dg/cpp23/constexpr-temp1.C: New test.
	* g++.dg/cpp23/constexpr-temp2.C: New test.
---
 gcc/cp/constexpr.c                           | 29 +++++++++++++--
 gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C  |  5 ++-
 gcc/testsuite/g++.dg/cpp0x/constexpr-temp2.C | 15 ++++++++
 gcc/testsuite/g++.dg/cpp23/constexpr-temp1.C | 39 ++++++++++++++++++++
 gcc/testsuite/g++.dg/cpp23/constexpr-temp2.C | 23 ++++++++++++
 5 files changed, 106 insertions(+), 5 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/constexpr-temp2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/constexpr-temp1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/constexpr-temp2.C


base-commit: f364cdffa47af574f90f671b2dcf5afa91442741

Comments

Jason Merrill July 16, 2021, 4:53 p.m. UTC | #1
On 7/15/21 5:14 PM, Marek Polacek wrote:
> The combination of DR 2481 and DR 2126 should allow us to do
> 
>    void f()
>    {
>      constexpr const int &r = 42;
>      static_assert(r == 42);
>    }
> 
> because [expr.const]/4.7 now says that "a temporary object of
> non-volatile const-qualified literal type whose lifetime is extended to
> that of a variable that is usable in constant expressions" is usable in
> a constant expression.
> 
> I think the temporary is supposed to be const-qualified, because Core 2481
> says so.  I was happy to find out that we already mark the temporary as
> const + constexpr in set_up_extended_ref_temp.
> 
> But that wasn't enough to make the test above work: references are
> traditionally implemented as pointers, so the temporary object will be
> (const int &)&D.1234, and verify_constant -> reduced_constant_expression_p
> -> initializer_constant_valid_p_1 doesn't think that's OK -- and rightly
> so -- the address of a local variable certainly isn't constant

Hmm.  I'm very sorry, I'm afraid I've steered you wrong repeatedly, and 
this is the problem with my testcase above and in the PR.

Making that temporary usable in constant expressions doesn't make it a 
valid initializer for the constexpr reference, because it is still not a 
"permitted result of a constant expression"; [expr.const]/11 still says 
that such an entity must have static storage duration.

So the above is only valid if the reference has static storage duration.

> Therefore
> I'm skipping the verify_constant check in cxx_eval_outermost_constant_expr.
> (DECL_INITIAL isn't checked because maybe we are still waiting for
> initialize_local_var to set it.)
> 
> Then we need to be able to evaluate such a reference.  This I do by
> seeing through the reference in cxx_eval_constant_expression.  I can't
> rely on decl_constant_value to pull out DECL_INITIAL, because the VAR_DECL
> isn't DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P, and I think we don't
> need to mess with that if we're keeping this purely in constexpr.
> 
> I wonder if we should accept
> 
>    void f2()
>    {
>      constexpr int &&r = 42;
>      static_assert(r == 42);
>    }
> 
> Currently we don't -- CP_TYPE_CONST_NON_VOLATILE_P (type) is false in
> set_up_extended_ref_temp.
> 
> Does this make sense?  Bootstrapped/regtested on x86_64-pc-linux-gnu.
> 
> 	PR c++/100976
> 	DR 2481
> 
> gcc/cp/ChangeLog:
> 
> 	* constexpr.c (cxx_eval_constant_expression): For a constexpr
> 	reference, return its DECL_INITIAL.
> 	(cxx_eval_outermost_constant_expr): Don't verify the initializer
> 	for a constexpr variable of reference type.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp0x/constexpr-ref2.C: Remove dg-error.
> 	* g++.dg/cpp0x/constexpr-temp2.C: New test.
> 	* g++.dg/cpp23/constexpr-temp1.C: New test.
> 	* g++.dg/cpp23/constexpr-temp2.C: New test.
> ---
>   gcc/cp/constexpr.c                           | 29 +++++++++++++--
>   gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C  |  5 ++-
>   gcc/testsuite/g++.dg/cpp0x/constexpr-temp2.C | 15 ++++++++
>   gcc/testsuite/g++.dg/cpp23/constexpr-temp1.C | 39 ++++++++++++++++++++
>   gcc/testsuite/g++.dg/cpp23/constexpr-temp2.C | 23 ++++++++++++
>   5 files changed, 106 insertions(+), 5 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp0x/constexpr-temp2.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/constexpr-temp1.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/constexpr-temp2.C
> 
> diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
> index 31fa5b66865..80b4985d055 100644
> --- a/gcc/cp/constexpr.c
> +++ b/gcc/cp/constexpr.c
> @@ -6180,6 +6180,22 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>   	  return cxx_eval_constant_expression (ctx, r, lval, non_constant_p,
>   					       overflow_p);
>   	}
> +      /* DR 2126 amended [expr.const]/4.7 to say that "a temporary object
> +	 of non-volatile const-qualified literal type whose lifetime is
> +	 extended to that of a variable that is usable in constant
> +	 expressions" is usable in a constant expression.  Along with
> +	 DR 2481 this means that we should accept
> +
> +	   constexpr const int &r = 42;
> +	   static_assert (r == 42);
> +
> +	 Take a shortcut here rather than using decl_constant_value.  The
> +	 temporary was marked constexpr in set_up_extended_ref_temp.  */
> +      else if (TYPE_REF_P (TREE_TYPE (t))
> +	       && DECL_DECLARED_CONSTEXPR_P (t)
> +	       && DECL_INITIAL (t))
> +	return cxx_eval_constant_expression (ctx, DECL_INITIAL (t), lval,
> +					     non_constant_p, overflow_p);
>         /* fall through */
>       case CONST_DECL:
>         /* We used to not check lval for CONST_DECL, but darwin.c uses
> @@ -7289,10 +7305,17 @@ cxx_eval_outermost_constant_expr (tree t, bool allow_non_constant,
>     r = cxx_eval_constant_expression (&ctx, r,
>   				    false, &non_constant_p, &overflow_p);
>   
> -  if (!constexpr_dtor)
> -    verify_constant (r, allow_non_constant, &non_constant_p, &overflow_p);
> -  else
> +  if (object && VAR_P (object)
> +      && DECL_DECLARED_CONSTEXPR_P (object)
> +      && TYPE_REF_P (TREE_TYPE (object)))
> +  /* Circumvent verify_constant, because it ends up calling
> +     initializer_constant_valid_p which doesn't like taking
> +     the address of a local variable.  But that's OK since
> +     DR 2126 + DR 2481, at least in a constexpr context.  */;
> +  else if (constexpr_dtor)
>       DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (object) = true;
> +  else
> +    verify_constant (r, allow_non_constant, &non_constant_p, &overflow_p);
>   
>     unsigned int i;
>     tree cleanup;
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C
> index 76973638d5f..b7701b849df 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C
> @@ -9,9 +9,10 @@ constexpr int& ri2 = er;	// { dg-error "er" }
>   
>   void f(int j)
>   {
> +  // Used to be erroneous, but allowed since DR 2481.
>     constexpr int i = 42;
> -  constexpr int const& ri = i;	// { dg-error "" }
> +  constexpr int const& ri = i;
>   
> -  constexpr int& rj = j;	// { dg-error "" }
> +  constexpr int& rj = j;

Even if my testcase were valid, these would still be errors, since 
neither of these involves a temporary.

>   }
>   
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-temp2.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-temp2.C
> new file mode 100644
> index 00000000000..e396264cff3
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-temp2.C
> @@ -0,0 +1,15 @@
> +// DR 2126 - Lifetime-extended temporaries in constant expressions
> +// { dg-do compile { target c++11 } }
> +
> +#define SA(X) static_assert ((X),#X)
> +
> +typedef const int CI[3];
> +constexpr CI &ci = CI{1, 2, 3};
> +SA(ci[1] == 2);
> +
> +void
> +g ()
> +{
> +  constexpr CI &r = CI{1, 2, 3};
> +  SA(r[1] == 2);
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/constexpr-temp1.C b/gcc/testsuite/g++.dg/cpp23/constexpr-temp1.C
> new file mode 100644
> index 00000000000..0d3f4972563
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/constexpr-temp1.C
> @@ -0,0 +1,39 @@
> +// PR c++/100976
> +// DR 2481 - Cv-qualification of temporary to which a reference is bound
> +// { dg-do compile { target c++11 } }
> +
> +#define SA(X) static_assert ((X),#X)
> +
> +struct literal {
> +    int m;
> +    constexpr literal() : m() { }
> +    constexpr literal(int n) : m(n) { }
> +};
> +
> +void
> +g ()
> +{
> +  constexpr const int &r = 42;
> +  constexpr const int &&rr = 42;
> +  constexpr int x = 42;
> +  constexpr static int sx = 42;
> +  constexpr const auto &rx = x;
> +  constexpr const auto &rsx = sx;
> +  SA(r == 42);
> +  SA(rr == 42);
> +  SA(rx == 42);
> +  SA(rsx == 42);
> +  SA(&rx == &x);
> +  SA(&rsx == &sx);
> +  const int *pr = &r;
> +  const int *prr = &rr;
> +}
> +
> +void
> +f ()
> +{
> +  constexpr const literal &r = { 42 };
> +  SA(r.m == 42);
> +  constexpr const literal &&rr = { 42 };
> +  SA(rr.m == 42);
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/constexpr-temp2.C b/gcc/testsuite/g++.dg/cpp23/constexpr-temp2.C
> new file mode 100644
> index 00000000000..1e1169ac5e1
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/constexpr-temp2.C
> @@ -0,0 +1,23 @@
> +// PR c++/100976
> +// DR 2481 - Cv-qualification of temporary to which a reference is bound
> +// { dg-do compile { target c++11 } }
> +
> +#define SA(X) static_assert ((X),#X)
> +
> +int
> +main ()
> +{
> +  constexpr const volatile int &r = 42; // { dg-error "cannot bind" }
> +  constexpr const volatile int &&rr = 42;
> +  constexpr int x = 42;
> +  constexpr const volatile auto &rx = x;
> +  SA(rr == 42); // { dg-error "non-constant|lvalue-to-rvalue" }
> +  SA(rx == 42); // { dg-error "non-constant|lvalue-to-rvalue" }
> +
> +  int i = 42;
> +  constexpr const auto &ri = i;
> +  SA(ri == 42); // { dg-error "non-constant|not usable" }
> +
> +  constexpr const int &missing; // { dg-error "not initialized" }
> +  SA ((bool(missing), true));
> +}
> 
> base-commit: f364cdffa47af574f90f671b2dcf5afa91442741
>
Marek Polacek July 16, 2021, 5:44 p.m. UTC | #2
On Fri, Jul 16, 2021 at 12:53:05PM -0400, Jason Merrill wrote:
> On 7/15/21 5:14 PM, Marek Polacek wrote:
> > The combination of DR 2481 and DR 2126 should allow us to do
> > 
> >    void f()
> >    {
> >      constexpr const int &r = 42;
> >      static_assert(r == 42);
> >    }
> > 
> > because [expr.const]/4.7 now says that "a temporary object of
> > non-volatile const-qualified literal type whose lifetime is extended to
> > that of a variable that is usable in constant expressions" is usable in
> > a constant expression.
> > 
> > I think the temporary is supposed to be const-qualified, because Core 2481
> > says so.  I was happy to find out that we already mark the temporary as
> > const + constexpr in set_up_extended_ref_temp.
> > 
> > But that wasn't enough to make the test above work: references are
> > traditionally implemented as pointers, so the temporary object will be
> > (const int &)&D.1234, and verify_constant -> reduced_constant_expression_p
> > -> initializer_constant_valid_p_1 doesn't think that's OK -- and rightly
> > so -- the address of a local variable certainly isn't constant
> 
> Hmm.  I'm very sorry, I'm afraid I've steered you wrong repeatedly, and this
> is the problem with my testcase above and in the PR.

No worries, in fact, I'm glad we don't have to introduce gross hacks into the
front end!

> Making that temporary usable in constant expressions doesn't make it a valid
> initializer for the constexpr reference, because it is still not a
> "permitted result of a constant expression"; [expr.const]/11 still says that
> such an entity must have static storage duration.

Indeed: "...if it is an object with static storage duration..."
And I never scrolled down to see that when looking at [expr.const] when looking
into this...

> So the above is only valid if the reference has static storage duration.

Is there anything we need to do to resolve DR 2481 and DR 2126, then?  Besides
adding this test:

  typedef const int CI[3];
  constexpr CI &ci = CI{11, 22, 33};
  static_assert(ci[1] == 22, "");

Marek
Jason Merrill July 17, 2021, 9:40 p.m. UTC | #3
On 7/16/21 1:44 PM, Marek Polacek wrote:
> On Fri, Jul 16, 2021 at 12:53:05PM -0400, Jason Merrill wrote:
>> On 7/15/21 5:14 PM, Marek Polacek wrote:
>>> The combination of DR 2481 and DR 2126 should allow us to do
>>>
>>>     void f()
>>>     {
>>>       constexpr const int &r = 42;
>>>       static_assert(r == 42);
>>>     }
>>>
>>> because [expr.const]/4.7 now says that "a temporary object of
>>> non-volatile const-qualified literal type whose lifetime is extended to
>>> that of a variable that is usable in constant expressions" is usable in
>>> a constant expression.
>>>
>>> I think the temporary is supposed to be const-qualified, because Core 2481
>>> says so.  I was happy to find out that we already mark the temporary as
>>> const + constexpr in set_up_extended_ref_temp.
>>>
>>> But that wasn't enough to make the test above work: references are
>>> traditionally implemented as pointers, so the temporary object will be
>>> (const int &)&D.1234, and verify_constant -> reduced_constant_expression_p
>>> -> initializer_constant_valid_p_1 doesn't think that's OK -- and rightly
>>> so -- the address of a local variable certainly isn't constant
>>
>> Hmm.  I'm very sorry, I'm afraid I've steered you wrong repeatedly, and this
>> is the problem with my testcase above and in the PR.
> 
> No worries, in fact, I'm glad we don't have to introduce gross hacks into the
> front end!
> 
>> Making that temporary usable in constant expressions doesn't make it a valid
>> initializer for the constexpr reference, because it is still not a
>> "permitted result of a constant expression"; [expr.const]/11 still says that
>> such an entity must have static storage duration.
> 
> Indeed: "...if it is an object with static storage duration..."
> And I never scrolled down to see that when looking at [expr.const] when looking
> into this...
> 
>> So the above is only valid if the reference has static storage duration.
> 
> Is there anything we need to do to resolve DR 2481 and DR 2126, then?  Besides
> adding this test:
> 
>    typedef const int CI[3];
>    constexpr CI &ci = CI{11, 22, 33};
>    static_assert(ci[1] == 22, "");

It seems not.

Jason
diff mbox series

Patch

diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
index 31fa5b66865..80b4985d055 100644
--- a/gcc/cp/constexpr.c
+++ b/gcc/cp/constexpr.c
@@ -6180,6 +6180,22 @@  cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
 	  return cxx_eval_constant_expression (ctx, r, lval, non_constant_p,
 					       overflow_p);
 	}
+      /* DR 2126 amended [expr.const]/4.7 to say that "a temporary object
+	 of non-volatile const-qualified literal type whose lifetime is
+	 extended to that of a variable that is usable in constant
+	 expressions" is usable in a constant expression.  Along with
+	 DR 2481 this means that we should accept
+
+	   constexpr const int &r = 42;
+	   static_assert (r == 42);
+
+	 Take a shortcut here rather than using decl_constant_value.  The
+	 temporary was marked constexpr in set_up_extended_ref_temp.  */
+      else if (TYPE_REF_P (TREE_TYPE (t))
+	       && DECL_DECLARED_CONSTEXPR_P (t)
+	       && DECL_INITIAL (t))
+	return cxx_eval_constant_expression (ctx, DECL_INITIAL (t), lval,
+					     non_constant_p, overflow_p);
       /* fall through */
     case CONST_DECL:
       /* We used to not check lval for CONST_DECL, but darwin.c uses
@@ -7289,10 +7305,17 @@  cxx_eval_outermost_constant_expr (tree t, bool allow_non_constant,
   r = cxx_eval_constant_expression (&ctx, r,
 				    false, &non_constant_p, &overflow_p);
 
-  if (!constexpr_dtor)
-    verify_constant (r, allow_non_constant, &non_constant_p, &overflow_p);
-  else
+  if (object && VAR_P (object)
+      && DECL_DECLARED_CONSTEXPR_P (object)
+      && TYPE_REF_P (TREE_TYPE (object)))
+  /* Circumvent verify_constant, because it ends up calling
+     initializer_constant_valid_p which doesn't like taking
+     the address of a local variable.  But that's OK since
+     DR 2126 + DR 2481, at least in a constexpr context.  */;
+  else if (constexpr_dtor)
     DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (object) = true;
+  else
+    verify_constant (r, allow_non_constant, &non_constant_p, &overflow_p);
 
   unsigned int i;
   tree cleanup;
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C
index 76973638d5f..b7701b849df 100644
--- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C
@@ -9,9 +9,10 @@  constexpr int& ri2 = er;	// { dg-error "er" }
 
 void f(int j)
 {
+  // Used to be erroneous, but allowed since DR 2481.
   constexpr int i = 42;
-  constexpr int const& ri = i;	// { dg-error "" }
+  constexpr int const& ri = i;
 
-  constexpr int& rj = j;	// { dg-error "" }
+  constexpr int& rj = j;
 }
 
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-temp2.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-temp2.C
new file mode 100644
index 00000000000..e396264cff3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-temp2.C
@@ -0,0 +1,15 @@ 
+// DR 2126 - Lifetime-extended temporaries in constant expressions
+// { dg-do compile { target c++11 } }
+
+#define SA(X) static_assert ((X),#X)
+
+typedef const int CI[3];
+constexpr CI &ci = CI{1, 2, 3};
+SA(ci[1] == 2);
+
+void
+g ()
+{
+  constexpr CI &r = CI{1, 2, 3};
+  SA(r[1] == 2);
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/constexpr-temp1.C b/gcc/testsuite/g++.dg/cpp23/constexpr-temp1.C
new file mode 100644
index 00000000000..0d3f4972563
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/constexpr-temp1.C
@@ -0,0 +1,39 @@ 
+// PR c++/100976
+// DR 2481 - Cv-qualification of temporary to which a reference is bound
+// { dg-do compile { target c++11 } }
+
+#define SA(X) static_assert ((X),#X)
+
+struct literal {
+    int m;
+    constexpr literal() : m() { }
+    constexpr literal(int n) : m(n) { }
+};
+
+void
+g ()
+{
+  constexpr const int &r = 42;
+  constexpr const int &&rr = 42;
+  constexpr int x = 42;
+  constexpr static int sx = 42;
+  constexpr const auto &rx = x;
+  constexpr const auto &rsx = sx;
+  SA(r == 42);
+  SA(rr == 42);
+  SA(rx == 42);
+  SA(rsx == 42);
+  SA(&rx == &x);
+  SA(&rsx == &sx);
+  const int *pr = &r;
+  const int *prr = &rr;
+}
+
+void
+f ()
+{
+  constexpr const literal &r = { 42 };
+  SA(r.m == 42);
+  constexpr const literal &&rr = { 42 };
+  SA(rr.m == 42);
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/constexpr-temp2.C b/gcc/testsuite/g++.dg/cpp23/constexpr-temp2.C
new file mode 100644
index 00000000000..1e1169ac5e1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/constexpr-temp2.C
@@ -0,0 +1,23 @@ 
+// PR c++/100976
+// DR 2481 - Cv-qualification of temporary to which a reference is bound
+// { dg-do compile { target c++11 } }
+
+#define SA(X) static_assert ((X),#X)
+
+int
+main ()
+{
+  constexpr const volatile int &r = 42; // { dg-error "cannot bind" }
+  constexpr const volatile int &&rr = 42;
+  constexpr int x = 42;
+  constexpr const volatile auto &rx = x;
+  SA(rr == 42); // { dg-error "non-constant|lvalue-to-rvalue" }
+  SA(rx == 42); // { dg-error "non-constant|lvalue-to-rvalue" }
+
+  int i = 42;
+  constexpr const auto &ri = i;
+  SA(ri == 42); // { dg-error "non-constant|not usable" }
+
+  constexpr const int &missing; // { dg-error "not initialized" }
+  SA ((bool(missing), true));
+}