c++: P2280R4, Using unknown refs in constant expr [PR106650]

Message ID 20231117214610.173872-1-polacek@redhat.com
State Accepted
Headers
Series c++: P2280R4, Using unknown refs in constant expr [PR106650] |

Checks

Context Check Description
snail/gcc-patch-check success Github commit url

Commit Message

Marek Polacek Nov. 17, 2023, 9:46 p.m. UTC
  Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --
This patch is an attempt to implement (part of?) P2280, Using unknown
pointers and references in constant expressions.  (Note that R4 seems to
only allow References to unknown/Accesses via this, but not Pointers to
unknown.)

This patch works to the extent that the test case added in [expr.const]
works as expected, as well as the test in
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2280r4.html#the-this-pointer>

Most importantly, the proposal makes this compile:

  template <typename T, size_t N>
  constexpr auto array_size(T (&)[N]) -> size_t {
      return N;
  }

  void check(int const (&param)[3]) {
      constexpr auto s = array_size(param);
      static_assert (s == 3);
  }

and I think it would be a pity not to have it in GCC 14.

What still doesn't work (and I don't know if it should) is the test in $3.2:

  struct A2 { constexpr int f() { return 0; } };
  struct B2 : virtual A2 {};
  void f2(B2 &b) { constexpr int k = b.f(); }

where we say
error: '* & b' is not a constant expression

	PR c++/106650

gcc/cp/ChangeLog:

	* constexpr.cc (cxx_eval_constant_expression): Allow reference to
	unknown as per P2280.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp0x/constexpr-array-ptr6.C: Remove dg-error.
	* g++.dg/cpp0x/constexpr-ref12.C: Likewise.
	* g++.dg/cpp1y/lambda-generic-const10.C: Likewise.
	* g++.dg/cpp0x/constexpr-ref13.C: New test.
	* g++.dg/cpp1z/constexpr-ref1.C: New test.
	* g++.dg/cpp1z/constexpr-ref2.C: New test.
	* g++.dg/cpp2a/constexpr-ref1.C: New test.
---
 gcc/cp/constexpr.cc                           |  2 +
 .../g++.dg/cpp0x/constexpr-array-ptr6.C       |  2 +-
 gcc/testsuite/g++.dg/cpp0x/constexpr-ref12.C  |  4 +-
 gcc/testsuite/g++.dg/cpp0x/constexpr-ref13.C  | 25 +++++++++
 .../g++.dg/cpp1y/lambda-generic-const10.C     |  2 +-
 gcc/testsuite/g++.dg/cpp1z/constexpr-ref1.C   | 26 +++++++++
 gcc/testsuite/g++.dg/cpp1z/constexpr-ref2.C   | 23 ++++++++
 gcc/testsuite/g++.dg/cpp2a/constexpr-ref1.C   | 54 +++++++++++++++++++
 8 files changed, 134 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/constexpr-ref13.C
 create mode 100644 gcc/testsuite/g++.dg/cpp1z/constexpr-ref1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp1z/constexpr-ref2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/constexpr-ref1.C


base-commit: 231bb992592a9e1bd7ce6583131acb1874c8e34e
  

Comments

Jason Merrill Nov. 20, 2023, 9:29 p.m. UTC | #1
On 11/17/23 16:46, Marek Polacek wrote:
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> 
> -- >8 --
> This patch is an attempt to implement (part of?) P2280, Using unknown
> pointers and references in constant expressions.  (Note that R4 seems to
> only allow References to unknown/Accesses via this, but not Pointers to
> unknown.)

Indeed.  That seems a bit arbitrary to me, but there it is.

We were rejecting the testcase before because 
cxx_bind_parameters_in_call was trying to perform an lvalue->rvalue 
conversion on the reference itself; this isn't really a thing in the 
language, but worked to implement the reference bullet that the paper 
removes.  Your approach to fixing that makes sense to me.

We should do the same for VAR_DECL references, e.g.

extern int (&r)[42];
constexpr int i = array_size (r);

You also need to allow (implict or explicit) use of 'this', as in:

struct A
{
   constexpr int f() { return 42; }
   void g() { constexpr int i = f(); }
};

> This patch works to the extent that the test case added in [expr.const]
> works as expected, as well as the test in
> <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2280r4.html#the-this-pointer>
> 
> Most importantly, the proposal makes this compile:
> 
>    template <typename T, size_t N>
>    constexpr auto array_size(T (&)[N]) -> size_t {
>        return N;
>    }
> 
>    void check(int const (&param)[3]) {
>        constexpr auto s = array_size(param);
>        static_assert (s == 3);
>    }
> 
> and I think it would be a pity not to have it in GCC 14.
> 
> What still doesn't work (and I don't know if it should) is the test in $3.2:
> 
>    struct A2 { constexpr int f() { return 0; } };
>    struct B2 : virtual A2 {};
>    void f2(B2 &b) { constexpr int k = b.f(); }
> 
> where we say
> error: '* & b' is not a constant expression

It seems like that is supposed to work, the problem is accessing the 
vtable to perform the conversion.  I have WIP to recognize that 
conversion better in order to fix PR53288; this testcase can wait for 
that fix.

Jason
  

Patch

diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index 344107d494b..d5e487801cc 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -7378,6 +7378,8 @@  cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
 	  r = build_constructor (TREE_TYPE (t), NULL);
 	  TREE_CONSTANT (r) = true;
 	}
+      else if (TYPE_REF_P (TREE_TYPE (t)))
+	/* P2280 allows references to unknown.	*/;
       else
 	{
 	  if (!ctx->quiet)
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-array-ptr6.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-array-ptr6.C
index 1c065120314..d212665e51f 100644
--- a/gcc/testsuite/g++.dg/cpp0x/constexpr-array-ptr6.C
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-array-ptr6.C
@@ -12,7 +12,7 @@  constexpr auto sz_d = size(array_double);
 static_assert(sz_d == 3, "Array size failure");
 
 void f(bool (&param)[2]) {
-  static_assert(size(param) == 2, "Array size failure"); // { dg-error "" }
+  static_assert(size(param) == 2, "Array size failure");
   short data[] = {-1, 2, -45, 6, 88, 99, -345};
   static_assert(size(data) == 7, "Array size failure");
 }
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ref12.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ref12.C
index 7c3ce66b4c9..f4500144946 100644
--- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ref12.C
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ref12.C
@@ -40,7 +40,7 @@  void f(a ap, a& arp)
   static_assert (g(ar2),"");	// { dg-error "constant" }
   static_assert (h(ar2),"");	// { dg-error "constant" }
 
-  static_assert (arp.g(),"");	// { dg-error "constant" }
-  static_assert (g(arp),"");	// { dg-error "constant" }
+  static_assert (arp.g(),"");
+  static_assert (g(arp),"");
   static_assert (h(arp),"");	// { dg-error "constant" }
 }
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ref13.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ref13.C
new file mode 100644
index 00000000000..4be729c2301
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ref13.C
@@ -0,0 +1,25 @@ 
+// P2280R4 - Using unknown pointers and references in constant expressions
+// PR c++/106650
+// { dg-do compile { target c++11 } }
+
+using size_t = decltype(sizeof(42));
+
+template <typename T, size_t N>
+constexpr auto array_size(T (&)[N]) -> size_t {
+    return N;
+}
+
+void check(int const (&param)[3]) {
+    int local[] = {1, 2, 3};
+    constexpr auto s0 = array_size(local);
+    constexpr auto s1 = array_size(param);
+}
+
+template <typename T, size_t N>
+constexpr size_t array_size_ptr(T (*)[N]) {
+    return N;
+}
+
+void check_ptr(int const (*param)[3]) {
+    constexpr auto s2 = array_size_ptr(param); // { dg-error "not a constant" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-const10.C b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-const10.C
index 2f48dae4746..47a49f58419 100644
--- a/gcc/testsuite/g++.dg/cpp1y/lambda-generic-const10.C
+++ b/gcc/testsuite/g++.dg/cpp1y/lambda-generic-const10.C
@@ -11,7 +11,7 @@  int main()
   constexpr auto x = f(); //ok, call constexpr const non-static method
 
   [](auto const &f) {
-    constexpr auto x = f();	// { dg-error "" }
+    constexpr auto x = f();
   }(f);
 
   [&]() {
diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-ref1.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-ref1.C
new file mode 100644
index 00000000000..82771814a80
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-ref1.C
@@ -0,0 +1,26 @@ 
+// P2280R4 - Using unknown pointers and references in constant expressions
+// PR c++/106650
+// { dg-do compile { target c++17 } }
+
+#include <type_traits>
+
+template <typename T, typename U>
+constexpr bool is_type(U &&)
+{
+    return std::is_same_v<T, std::decay_t<U>>;
+}
+
+auto visitor = [](auto&& v) {
+    if constexpr(is_type<int>(v)) {
+        // ...
+    } else if constexpr(is_type<char>(v)) {
+        // ...
+    }
+};
+
+void
+g (int i)
+{
+  visitor (i);
+  constexpr bool b = is_type<int>(i);
+}
diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-ref2.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-ref2.C
new file mode 100644
index 00000000000..ca734378141
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-ref2.C
@@ -0,0 +1,23 @@ 
+// P2280R4 - Using unknown pointers and references in constant expressions
+// PR c++/106650
+// { dg-do compile { target c++17 } }
+
+template <bool V>
+struct Widget {
+   struct Config {
+      static constexpr bool value = V;
+   } config;
+
+   void f() {
+       if constexpr (config.value) {
+          // ...
+       }
+   }
+};
+
+void
+g ()
+{
+  Widget<false> w;
+  w.f();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-ref1.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-ref1.C
new file mode 100644
index 00000000000..36866306684
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-ref1.C
@@ -0,0 +1,54 @@ 
+// P2280R4 - Using unknown pointers and references in constant expressions
+// PR c++/106650
+// { dg-do compile { target c++20 } }
+
+#include <typeinfo>
+
+using size_t = decltype(sizeof(42));
+
+template <typename T, size_t N>
+constexpr size_t array_size(T (&)[N]) {
+  return N;
+}
+
+void use_array(int const (&gold_medal_mel)[2]) {
+  constexpr auto gold = array_size(gold_medal_mel);     // OK
+}
+
+constexpr auto olympic_mile() {
+  const int ledecky = 1500;
+  return []{ return ledecky; };
+}
+static_assert(olympic_mile()() == 1500);                // OK
+
+struct Swim {
+  constexpr int phelps() { return 28; }
+  virtual constexpr int lochte() { return 12; }
+  int coughlin = 12;
+};
+
+constexpr int how_many(Swim& swam) {
+  Swim* p = &swam;
+  return (p + 1 - 1)->phelps();
+}
+
+void splash(Swim& swam) {
+  static_assert(swam.phelps() == 28);           // OK
+  static_assert((&swam)->phelps() == 28);       // OK
+
+  Swim* pswam = &swam;
+  static_assert(pswam->phelps() == 28);         // { dg-error "non-constant|not usable" }
+
+  static_assert(how_many(swam) == 28);          // OK
+  static_assert(Swim().lochte() == 12);         // OK
+
+  static_assert(swam.lochte() == 12);           // { dg-error "non-constant|not a constant" }
+
+  static_assert(swam.coughlin == 12);           // { dg-error "non-constant|not a constant" }
+}
+
+extern Swim dc;
+extern Swim& trident;
+
+constexpr auto& sandeno   = typeid(dc);         // OK, can only be typeid(Swim)
+constexpr auto& gallagher = typeid(trident);    // { dg-error "not usable" }