C++ PATCH for c++/87567, constexpr rejects call to non-constexpr function

Message ID 20181010155850.GD19003@redhat.com
State New
Headers show
Series
  • C++ PATCH for c++/87567, constexpr rejects call to non-constexpr function
Related show

Commit Message

Marek Polacek Oct. 10, 2018, 3:58 p.m.
In this testcase, the call to f() can never be a constant expression, but
that's not a problem because it is never reached.  We handle a similar scenario
for IF_STMT, so we can just do the same.  The RANGE_FOR_STMT case seems to
never be reached in the whole testsuite, so I didn't change that.

Bootstrapped/regtested on x86_64-linux, ok for trunk?

2018-10-10  Marek Polacek  <polacek@redhat.com>

	PR c++/87567 - constexpr rejects call to non-constexpr function.
	* constexpr.c (potential_constant_expression_1) <case FOR_STMT>: Return
	true if the condition is always false.
	<case WHILE_STMT>: Likewise.

	* g++.dg/cpp1y/constexpr-loop7.C: New test.

Comments

Jason Merrill Oct. 10, 2018, 7:48 p.m. | #1
On Wed, Oct 10, 2018 at 11:58 AM Marek Polacek <polacek@redhat.com> wrote:
>
> In this testcase, the call to f() can never be a constant expression, but
> that's not a problem because it is never reached.  We handle a similar scenario
> for IF_STMT, so we can just do the same.  The RANGE_FOR_STMT case seems to
> never be reached in the whole testsuite, so I didn't change that.

That seems like a hole in the testsuite.  How about this?

constexpr int
fn3()
{
  struct empty_range {
    constexpr int* begin() { return 0; }
    constexpr int* end() { return 0; }
  } e;
  for (auto x : e)
    f();
  return 0;
}

The patch is OK; you can address fn3 in a follow-up patch.

Jason
Marek Polacek Oct. 10, 2018, 9:06 p.m. | #2
On Wed, Oct 10, 2018 at 03:48:34PM -0400, Jason Merrill wrote:
> On Wed, Oct 10, 2018 at 11:58 AM Marek Polacek <polacek@redhat.com> wrote:
> >
> > In this testcase, the call to f() can never be a constant expression, but
> > that's not a problem because it is never reached.  We handle a similar scenario
> > for IF_STMT, so we can just do the same.  The RANGE_FOR_STMT case seems to
> > never be reached in the whole testsuite, so I didn't change that.
> 
> That seems like a hole in the testsuite.  How about this?
> 
> constexpr int
> fn3()
> {
>   struct empty_range {
>     constexpr int* begin() { return 0; }
>     constexpr int* end() { return 0; }
>   } e;
>   for (auto x : e)
>     f();
>   return 0;
> }
> 
> The patch is OK; you can address fn3 in a follow-up patch.

Thanks.  fn3() still only triggers case FOR_STMT, because RANGE_FOR_STMT are
only created in templates, and are converted to FOR_STMTs in tsubst_expr.

But I've now found a testcase that triggers the RANGE_FOR_STMT case:

int f() { return 1; }

template<typename>
constexpr void
fn4 ()
{
  struct empty_range {
    constexpr int* begin() { return 0; }
    constexpr int* end() { return 0; }
  } e;
  constexpr int j = ({ for (auto x : e) f(); 1; });
}

void
fn5 ()
{
  fn4<int>();
}

However, we fail to compile both fn3 and fn4, because we're unable to evaluate
__for_begin != __for_end.  I'm going to try to figure out why.

Marek

Patch

diff --git gcc/cp/constexpr.c gcc/cp/constexpr.c
index 403edda8c47..4fa8c965a9d 100644
--- gcc/cp/constexpr.c
+++ gcc/cp/constexpr.c
@@ -5818,8 +5818,16 @@  potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
     case FOR_STMT:
       if (!RECUR (FOR_INIT_STMT (t), any))
 	return false;
-      if (!RECUR (FOR_COND (t), rval))
+      tmp = FOR_COND (t);
+      if (!RECUR (tmp, rval))
 	return false;
+      if (tmp)
+	{
+	  if (!processing_template_decl)
+	    tmp = cxx_eval_outermost_constant_expr (tmp, true);
+	  if (integer_zerop (tmp))
+	    return true;
+	}
       if (!RECUR (FOR_EXPR (t), any))
 	return false;
       if (!RECUR (FOR_BODY (t), any))
@@ -5840,8 +5848,13 @@  potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
       return true;
 
     case WHILE_STMT:
-      if (!RECUR (WHILE_COND (t), rval))
+      tmp = WHILE_COND (t);
+      if (!RECUR (tmp, rval))
 	return false;
+      if (!processing_template_decl)
+	tmp = cxx_eval_outermost_constant_expr (tmp, true);
+      if (integer_zerop (tmp))
+	return true;
       if (!RECUR (WHILE_BODY (t), any))
 	return false;
       if (breaks (jump_target) || continues (jump_target))
diff --git gcc/testsuite/g++.dg/cpp1y/constexpr-loop7.C gcc/testsuite/g++.dg/cpp1y/constexpr-loop7.C
index e69de29bb2d..6733820fee2 100644
--- gcc/testsuite/g++.dg/cpp1y/constexpr-loop7.C
+++ gcc/testsuite/g++.dg/cpp1y/constexpr-loop7.C
@@ -0,0 +1,21 @@ 
+// PR c++/87567
+// { dg-do compile { target c++14 } }
+
+constexpr bool always_false() { return false; }
+int f() { return 1; }
+
+constexpr int
+fn1 ()
+{
+  while (always_false ())
+    return f();
+  return 0;
+}
+
+constexpr int
+fn2 ()
+{
+  for (;always_false();)
+    return f();
+  return 0;
+}