Is a compiler forced to reject invalid constexpr?

2020-08-26 03:14发布

问题:

#include <exception>

constexpr bool foo(bool x)
{
  return x ? true : throw std::exception();
}

int main()
{
  // 1) must never be compiled
  // static_assert(foo(false), "");

  // 2) must always be compiled?
  const bool x = foo(false);

  // 3) must never compile?
  constexpr bool y = foo(false);

  return 0;
}

I'm sure that (1) must lead to a compile error. I'm quite sure that (2) must not be rejected at compile time, though it will fail at runtime.

The interesting case is the constexpr variable (3). In this simple example, gcc and clang actually evaluate the expression, and will therefore reject the program. (Error message: y is not a constant expression).

Is every C++11 compiler forced to reject the program? What if foo(false) was replaced by a more complex expression?

I was suprised to find out that constexpr were not turing-complete, though it will be after a change in the specification: Is constexpr-based computation Turing complete?

Maybe this is related to my question. As far as I understand, the compiler is allowed to postpone the actual evaluation of the constexpr (3) in this example until runtime. But if constexpr are turing-complete, I find it hard to believe that the compiler can decide for all constexpr whether an exception will be thrown (which means that the constexpr is invalid).

回答1:

By my reading, yes, every compiler must complain about statement (3).

N3242 7.1.5 paragraph 9:

A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. If it initialized by a constructor call, that call shall be a constant expression (5.19). Otherwise, every full-expression that appears in its initializer shall be a constant expression. Each implicit conversion used in converting the initializer expressions and each constructor call used for the initialization shall be one of those allowed in a constant expression (5.19).

I think of a constexpr object as "evaluated at compile time", and a constexpr function or constexpr constructor as "might be evaluated at compile time". A compiler must determine the semantic validity of statements like (3) at compile time. You could argue that the "evaluation" can still be done at run time, but checking for validity does most of that work anyway. Plus, the code could then continue to instantiate a template like Check<y>, which pretty much guarantees the compiler needs to figure out the value of y at compile-time.

This does mean you could write a diabolical program to make the compiler take a really long or infinite time. But I suspect that was already possible with operator-> tricks.



回答2:

I'm sure that (1) must lead to a compile error. I'm quite sure that (2) must not be rejected at compile time, though it will fail at runtime.

Correct. The throw part of the conditional operator is not a constant expression, and in (1), it's not unevaluated. For (2), foo is not forced to be evaluated at compile-time.

For (3), how would the compiler be allowed to post-pone evaluation? The constexpr decl-specifier forces foo to be evaluated at compile-time. It's basically the same as (1), initialization of y is a context where a constant expression is required.

§7.1.6 [dcl.constexpr] p9

A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. If it is initialized by a constructor call, that call shall be a constant expression (5.19). Otherwise, or if a constexpr specifier is used in a reference declaration, every full-expression that appears in its initializer shall be a constant expression. [...]