Throw in Constexpr Function

Throw in constexpr function

clang is correct, note the HEAD revision of gcc accepts also accepts this code. This is a well-formed constexpr function, as long as there is value for the argument(s) that allows the function to be evaluated as a core constant expression. In your case 1 is such a value.

This is covered in the draft C++14 standard section 7.1.5 The constexpr specifier [dcl.constexpr] which tells us what is allowed in a constexpr function:

The definition of a constexpr function shall satisfy the following constraints:

  • it shall not be virtual (10.3);

  • its return type shall be a literal type;

  • each of its parameter types shall be a literal type;

  • its function-body shall be = delete, = default, or a compound-statement that does not contain

    • an asm-definition,

    • a goto statement,

    • a try-block, or

    • a definition of a variable of non-literal type or of static or thread storage duration or for which
      no initialization is performed.

no restriction on throw and it also says (emphasis mine):

For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting
constexpr constructor, if no argument values exist such that an invocation of the function or constructor
could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed
; no
diagnostic required.

and below this paragraph we have the following example, similar to yours:

constexpr int f(bool b)
{ return b ? throw 0 : 0; } // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required

throw is not allowed in a core constant expression, which is covered in section 5.19 [expr.const] paragraph 2 which says:

A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the
abstract machine (1.9), would evaluate one of the following expressions

and includes the following bullet:

  • a throw-expression (15.1).

and so f would not be usable in a core constant expression when n <= 0.

Update

As TemplateRex points out, there are two gcc bugs reports for this:

  • Never executed "throw" in constexpr function fails to compile
  • C++14] throw-expression is not a valid constant-expression

TemplateRex also notes the fixes are not applied to to 5.3.0 and are only in trunk. No, work arounds are provided.

What happens when an exception is thrown while computing a constexpr?

The initialiser for a constexpr variable must be a constant expression (C++11 §7.1.5/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 is initialized by a constructor call, [...]. 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.

Note the following requirements for 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, but subexpressions of [...] conditional operations that are not evaluated are not considered

  • [...]

  • an invocation of a constexpr function with arguments that, when substituted by function invocation substitution (7.1.5), do not produce a constant expression;

  • [...]

  • a throw-expression (15.1).

Function invocation substitution for a constexpr function is defined as follows (§7.1.5/5):

Function invocation substitution for a call of a constexpr function [...] means implicitly converting each argument to the corresponding parameter type as if by copy-initialization, substituting that converted expression for each use of the corresponding parameter in the function-body, and [...] implicitly converting the resulting returned expression or braced-init-list to the return type of the function as if by copy-initialization. Such substitution does not change the meaning.

As we saw above (§5.19/2), subexpressions of conditional operations that are not evaluated are not considered. f(42) is not a constant expression because when you perform function invocation substitution on f, it results in an expression with a throw expression on the side of the conditional operation that is evaluated. On the other hand, for f(41), the throw ends up on the side that isn't evaluated.

So the program is ill-formed. It doesn't matter whether the initialiser is actually reached or not because the program shouldn't compile.

Optional throw in constexpr?

It seems it is trying to enforce the function being used only in compile time, according to this comment in another of the functions of the library:

// convenience function for inferring the string size and ensuring no
// accidental runtime encryption
template <uint64_t S, size_t N>
constexpr encrypted_string<S, N> make_encrypted_string(const char(&s)[N])
{
return true ? encrypted_string<S, N>(s) :
throw err::strenc_runtime_error;
}

However, as you point out, it is not doing anything here. Typically, that trick with the ternary operator in constexpr functions is used to trigger compile-time errors given a condition -- not to ensure all calls to the function are constant expressions. See constexpr error at compile-time, but no overhead at run-time for an explanation of that pattern.

If you need to ensure that the result was found out during compile time, you can easily assign the result to a constexpr variable:

constexpr int result = strlen("asd");

What happens when there's an error in constexpr function?

constexpr it means that it can be evaluated at compile time, not that it will be evaluated at compile time. The compiler will be forced to do the evaluation compile-time if you use it where a compile time constant is expected (e.g. the size of an array).

On the other hand for small values g++ is for example smart enough to compute the result compile time (even without constexpr).

For example with:

int fact(int n) {
return n < 2 ? 1 : n*fact(n-1);
}

int bar() {
return fact(5);
}

the code generated by g++ -O3 for bar is:

bar():
mov eax, 120
ret

Note that overflowing the call stack (e.g. infinite or excessive recursion) or even overflowing signed integer arithmetic is in C++ undefined behavior and anything can happen. It doesn't mean you'll get a nice "error" or even a segfault... but that ANYTHING can happen (including, unfortunately, nothing evident). Basically it means that the authors of compilers can just ignore to handle those cases because you're not supposed to do this kind of mistakes.

constexpr error at compile-time, but no overhead at run-time

Is there a way to cause a compile-time error with a constexpr function, but not do anything at run time?

You can use the exact same trick, but instead of using a throw-expression, use an expression that is not a constant expression but does what you want at runtime. For instance:

int runtime_fallback(int x) { return x; } // note, not constexpr
constexpr int f(int x) {
return (x != 0) ? x : runtime_fallback(0);
}

constexpr int k1 = f(1); // ok
constexpr int k2 = f(0); // error, can't call 'runtime_fallback' in constant expression
int k3 = f(0); // ok

Do relaxed constexpr rules in C++1y (C++14) change anything?

Not in this area, no. There are some forms of expression that are valid in constant expressions in C++14 but not in C++11, but neither throw-expressions nor calls to non-constexpr functions are on that list.

How to prevent accidental emission of constexpr functions

The problem is you actually instantiate (call constructor) a struct which is of incomplete type. This trick you talk about requires any symbol which will not be found at link time. So instead of struct you may use int:

http://coliru.stacked-crooked.com/a/3df5207827c8888c

#include <iostream>

extern int Exc;

constexpr int foo( int a )
{
if( a == 42 )
{
throw Exc;
}

return 666;
}

int main()
{
// Compiles
constexpr auto ret = foo(43);
std::cout << ret << "\n";

// This will show linker error as expected:
// /tmp/ccQfT6hd.o: In function `main':
// main.cpp:(.text.startup+0x4c): undefined reference to `Exc'
// collect2: error: ld returned 1 exit status
int nn;
std::cin >> nn;
auto ret2 = foo(nn);

return ret;
}

Provide constexpr-safe simplified exception message when consteval'd, otherwise stringstream verbose info

You might move the error message creation in another function:

std::string get_error_message(int index, int length = 10)
{
std::stringstream stream;
stream << "You did a bad. getItem was called with an invalid index ("
<< index
<< "), but it should have been non-negative "
<< "and less than the total number of items ("
<< length << ").";
return stream.str();
}

constexpr float getItem(int index)
{
constexpr int length = 10;
constexpr std::array<float, length> items{};

if (index < 0 || index >= length)
{
throw std::runtime_error(get_error_message(index, length));
}
return items[index];
}

Demo



Related Topics



Leave a reply



Submit