On Mon, Nov 20, 2023 at 04:29:33PM -0500, Jason Merrill wrote: > 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);
Argh, right. > 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(); } > }; Ah, I thought that already worked, but not so. Apology apology. > > 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 (¶m)[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. Great. 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 (¶m)[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 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 This will be fixed in the future. PR c++/106650 gcc/cp/ChangeLog: * constexpr.cc (cxx_eval_constant_expression) <case PARM_DECL>: Allow reference to unknown/this as per P2280. <case VAR_DECL>: 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/cpp0x/constexpr-ref2.C: Adjust dg-error. * g++.dg/cpp0x/noexcept34.C: Remove dg-error. * 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 | 8 ++- .../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 | 41 ++++++++++++++ gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C | 4 +- gcc/testsuite/g++.dg/cpp0x/noexcept34.C | 8 +-- .../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 +++++++++++++++++++ 10 files changed, 161 insertions(+), 11 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 diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index 344107d494b..b17e176aded 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -7336,7 +7336,9 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, if (TREE_CODE (r) == TARGET_EXPR && TREE_CODE (TARGET_EXPR_INITIAL (r)) == CONSTRUCTOR) r = TARGET_EXPR_INITIAL (r); - if (DECL_P (r)) + if (DECL_P (r) + /* P2280 allows references to unknown. */ + && !(VAR_P (t) && TYPE_REF_P (TREE_TYPE (t)))) { if (!ctx->quiet) non_const_var_error (loc, r, /*fundef_p*/false); @@ -7378,6 +7380,10 @@ 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 (is_this_parameter (t)) + /* ...as well as the this pointer. */; 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 (¶m)[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..f26027552a0 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ref13.C @@ -0,0 +1,41 @@ +// 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; +} + +extern int (&r)[42]; +constexpr int i = array_size (r); + +void check(int const (¶m)[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" } +} + +struct A +{ + constexpr int f() { return 42; } + void g() { constexpr int i = f(); } + void g2() { constexpr int i = this->f(); } +}; + +struct B { + constexpr static bool b = false; + void g() noexcept(b) { } + void g2() noexcept(this->b) { } +}; diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C index 76973638d5f..d5327c2dcb2 100644 --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C @@ -4,8 +4,8 @@ extern int *p; constexpr int& ri = *p; // { dg-error "p" } -extern constexpr int &er; // { dg-error "not a definition" } -constexpr int& ri2 = er; // { dg-error "er" } +extern constexpr int &er; // { dg-error "not a definition|not a constant" } +constexpr int& ri2 = er; void f(int j) { diff --git a/gcc/testsuite/g++.dg/cpp0x/noexcept34.C b/gcc/testsuite/g++.dg/cpp0x/noexcept34.C index 963881b5ad6..5cb99675fd5 100644 --- a/gcc/testsuite/g++.dg/cpp0x/noexcept34.C +++ b/gcc/testsuite/g++.dg/cpp0x/noexcept34.C @@ -7,13 +7,13 @@ template<typename> struct A { constexpr int f () { return 0; } bool b = true; - void g () noexcept (f()) { } // { dg-error ".this. is not a constant" } - void g2 () noexcept (this->f()) { } // { dg-error ".this. is not a constant" } + void g () noexcept (f()) { } + void g2 () noexcept (this->f()) { } void g3 () noexcept (b) { } // { dg-error "use of .this. in a constant expression|use of parameter|.this. is not a constant" } void g4 (int i) noexcept (i) { } // { dg-error "use of parameter" } - void g5 () noexcept (A::f()) { } // { dg-error ".this. is not a constant" } + void g5 () noexcept (A::f()) { } void g6 () noexcept (foo(b)) { } // { dg-error "use of .this. in a constant expression|use of parameter|.this. is not a constant" } - void g7 () noexcept (int{f()}) { } // { dg-error ".this. is not a constant" } + void g7 () noexcept (int{f()}) { } }; int main () 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..2ea865f8d5a --- /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 a constant" } base-commit: 634cf26c94de620e66aa124b8ec4d6c2be4b74b2 -- 2.42.0