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 asconst
. Such an object shall have literal type and shall be initialized. If it is initialized by a constructor call, [...]. Otherwise, or if aconstexpr
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
Createprocess Doesn't Pass Command Line Arguments
Why Does Reallocating a Vector Copy Instead of Moving the Elements
How to Ensure That the Template Parameter Is a Subtype of a Desired Type
How to Select a Random Element in Std::Set
Order of Calling Base Class Constructor from Derived Class Initialization List
Cannot Convert 'Const Char*' to 'Lpcwstr {Aka Const Wchar_T*}'
How to Delete[] a Pointer That Points into an Allocated Array, But Not to the Start of It
Difference Between Initialization of Static Variables in C and C++
Why Calling Main() Is Not Allowed in C++
Reason for Using Non-Type Template Parameter Instead of Regular Parameter
How to Call Derived Class Method from Base Class Pointer
Maximum Stack Size for C/C+ Program
Issue with Cin When Spaces Are Inputted, Using String Class
Windows Unicode C++ Stream Output Failure
Math Precision Requirements of C and C++ Standard
Stopping the Debugger When a Nan Floating Point Number Is Produced