0

Is the compiler guaranteed to evaluate boolean constexpr expressions that are "tautologies" (e.g. always true or false respectively) in a constexpr environment?

Minimal Example / Clarification

For example, in the following code snipplet (at the line marked with (1)) I invoke a function in a constexpr environment, which I intend to cause a compile time error whenever a non-constexpr function is passed. At least the compiler I use (g++-10.0) does this, even though it could also realize that the expression is always true without evaluating it. The reason I ask this question, is because -- to the best of my knowledge -- in a non-constepxr context, an expression like i >= std::numeric_limits<int>::min() is optimized away to true for an int i .

#include <limits>
constexpr int example_function() { return 1;}
constexpr bool compileTimeErrorDesired = example_function() || true; // (1)

Application Example

If the behavior in (1) is guaranteed, it can for instance be used in a concept, in order to execute different code, depending on whether a function provided as template argument can be evaluated at compile time. I implemented a very short (7 lines-of-code) example which does exactly that here at compiler explorer.

Question

Is the line (1) guaranteed to cause a compile time error if called with a non-constexpr function?

edit Inserted clarification, simplify example due to feedback.

mutableVoid
  • 1,284
  • 2
  • 11
  • 29
  • 4
    You mean when `example_function` is not constexpr? `example_function() || true` is not a constant expression if `example_function` is not constexpr, and compilers are required to diagnose non-constant expression to `static_assert`. – L. F. Mar 24 '20 at 12:53
  • 1
    What do you mean by "with a more obvious tautology, no compile time error is thrown"? If `example_function` is not declared `constexpr`, then g++ does correctly reject `static_assert(example_function() || true)`. – aschepler Mar 24 '20 at 13:05
  • @L.F. yes, if example_function is not constexpr, the code above is supposed to fail, which it does not when I assert `example_function() || true ` even though it is not a constant expression. So the fact that gcc does not fail while trying to evaluate the assertion in that case is a compiler bug? – mutableVoid Mar 24 '20 at 13:06
  • @L.F. I rechecked that with the compiler at compilerexplorer, and I think this was due to a compiler error at my machine: At compilerExplroer's version of gcc-trunk, a compile time error is thrown in the line with the static assertion, just as I wanted it to. I will update my question in order to reflect that, sorry. – mutableVoid Mar 24 '20 at 13:10
  • @aschepler You are right. When I wrote the question I did not receive a compile time error with my compiler at my local machine, I rechecked on compiler_explorer and it also throws a compile time error for "example_function() || true" which was intended. My question is: is this behavior guaranteed? – mutableVoid Mar 24 '20 at 13:15
  • 3
    Is what behavior guaranteed? I don't understand, at all, what the question is asking. – Barry Mar 24 '20 at 13:17
  • 2
    It's guaranteed. `example_function() || true` is not a *constant expression* according to C++ rules, even if its value is mathematically constant. – L. F. Mar 24 '20 at 13:18
  • 1
    IMO problem in fact refers to "AS IF rule" which is nicely [explained here](https://stackoverflow.com/a/15718279/1387438). Also [order of evaluating operator arguments](http://www.enseignement.polytechnique.fr/informatique/INF478/docs/Cpp/en/cpp/language/eval_order.html) has impact on this issue. – Marek R Mar 24 '20 at 13:29

2 Answers2

5

It's guaranteed that f() || true is not a core constant expression if f is not a constexpr function: (constant expressions are more restrictive than core constant expressions) [expr.const]/2.2

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

  • [...]

  • an invocation of a function other than a constexpr constructor for a literal class, a constexpr function, or an implicit invocation of a trivial destructor ([class.dtor]) [ Note: Overload resolution is applied as usual  — end note ];

  • [...]

It's also guaranteed that the program is ill-formed (diagnostic required) if non-constant expressions are used in contexts that require constant expressions. Note that || is defined to evaluate from left to right: [expr.log.or]/1

The || operator groups left-to-right. The operands are both contextually converted to bool. It returns true if either of its operands is true, and false otherwise. Unlike |, || guarantees left-to-right evaluation; moreover, the second operand is not evaluated if the first operand evaluates to true.

In other words, true || f() is a core constant expression because f() is not evaluated, whereas f() || true is not because f() is evaluated.

Whether or not an expression is a constant expression has nothing to do with optimization — constant expressions are defined based on the rules of the abstract machine.

L. F.
  • 19,445
  • 8
  • 48
  • 82
2

The terms "executed" isn't exactly appropriate when it comes to constant expressions. Even "evaluated" might be used with care, because whether an expression is a constant expression depends partly on behavior of what would happen if the expression were evaluated, but is not considered an evaluation in the most strict sense.

[expr.const] describes requirements for a number of different contexts of "compile-time behavior", including "constant expression". [expr.const]/(5.2) says that if evaluating an expression would evaluate a non-constexpr function, the expression is not a core constant expression, and therefore not a constant expression. If the expression is used in a context requiring a constant expression (like static_assert, a non-type template argument, etc.), the program is ill-formed and there must be a diagnostic message. There is no rule permitting anything like allowing a non-constant expression in such a context or skipping some parts of the hypothetical evaluation if the expression is a converted constant expression and the result value of the expression can be determined despite not being a constant expression.

So if example_function is not declared constexpr, then example_function() || true is not a constant expression because the evaluation would require calling the function. But true || example_function() is a constant expression, since the evaluation would not call the function.

Your is_constexpr<T> is guaranteed to work, because any semantic constraint violation involving a template parameter inside a requires-expression does not make the program ill-formed, but just makes the requires-expression result value false ([expr.prim.req]/6). In is_constexpr<example_function>, using the non-constant expression T() as a template argument to std::enable_if via the default template argument instantiated by ConstexprHelper<T> is such a semantic error, so the requires-expression has value false.

aschepler
  • 70,891
  • 9
  • 107
  • 161