Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it legal to use side-effects in exceptions thrown by constexpr?

Normally, constexpr must be free of side-effects. However, I just discovered that it is possible to use side-effects in the constructors of thrown exceptions. That technique can be used to emulate assert() for constexpr functions, as it is demonstrated in the following program.

#include <iostream>
#include <cstdlib>
#include <stdexcept>

struct constexpr_precond_violated : std::logic_error
{
  constexpr_precond_violated(const char* msg) :
    std::logic_error(msg)
  {
    std::cerr << msg << '\n';
    abort(); // to get a core dump
  }
};

#define TO_STRING_IMPL(x) #x
#define TO_STRING(x) TO_STRING_IMPL(x)

#define CONSTEXPR_PRECOND(cond, value) \
  ((!(cond)) ? throw constexpr_precond_violated( \
    "assertion: <" #cond "> failed (file: " \
    __FILE__ ", line: " TO_STRING(__LINE__) ")")    \
   : (value))

constexpr int divide(int x, int y)
{
  return CONSTEXPR_PRECOND(y != 0, x / y);
}

int main(int argc, char** argv)
{
  // The compiler cannot know argc, so it must be evaluated at runtime.
  // If argc is 2, the precondition is violated.
  return divide(100, argc - 2);
}

I tested it with g++ 4.7.2 and clang++ 3.1. When the preconditions fails, you get the error location and a core dump.

./constexpr_assert some_arg
assertion: <y != 0> failed (file: constexpr_assert.cpp, line: 26)
Aborted (core dumped)

So it works with the current compilers, but is it legal C++11?

like image 463
Philipp Claßen Avatar asked Oct 23 '12 20:10

Philipp Claßen


People also ask

Can Constexpr throw exception?

Even though try blocks and inline assembly are allowed in constexpr functions, throwing exceptions or executing the assembly is still disallowed in a constant expression.

Which is false about Constexpr?

Short answer: static_assert(false) should never appear in a constexpr if expression, regardless of whether it's in a template function or whether it's in the discarded branch.

Is Constexpr a Noexcept?

No, in general it doesn't. A constexpr function can be invoked in a non-constepr context in which it is allowed to throw an exception (except of course if you manually specify it to be noexcept(true) ).

Can Constexpr be modified?

According to the proposal, an object created within a constexpr expression, can now be changed during the evaluation process - until the evaluation process or the object's lifetime ends.


1 Answers

It is legal.

For each constexpr function there must be some argument values that result in a constant expression (§7.1.5/5):

For a constexpr function, if no function argument values exist such that the function invocation substitution would produce a constant expression (5.19), the program is ill-formed; no diagnostic required.

Note that this does not mean that every possible argument value must result in a constant expression. divide clearly has some argument values that result in a constant expression: divide(1, 1) is a simple example. So, the definition is clearly valid.

But can divide(1, 0) be called? Yes, it can. There's almost no difference between invoking a constexpr function or a "normal" function (§7.1.5/7):

A call to a constexpr function produces the same result as a call to an equivalent non-constexpr function in all respects except that a call to a constexpr function can appear in a constant expression.

Note that calls to constexpr functions can appear in constant expressions, but nothing forbids them from not resulting in constant expressions. This is intended so one can call constexpr functions with both compile-time and runtime arguments (otherwise usefulness of constexpr would be severaly limited).

For completeness, let's see what makes a constant expression (§5.19/2):

A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression (§3.2), but subexpressions of logical AND (§5.14), logical OR (§5.15), and conditional (§5.16) operations that are not evaluated are not considered [...].

So, divide(1, 1) is a constant expression, but divide(1, 0) is not. If you used divide(1, 0) in a template parameter, the program would be ill-formed. But otherwise it's fine.

like image 77
R. Martinho Fernandes Avatar answered Oct 27 '22 05:10

R. Martinho Fernandes