I claim that this program ought to be well-formed: it declares a constexpr member function of S<int>
. However, both GCC and Clang reject this program.
template<class T>
struct S {
constexpr int foo() {
if constexpr (std::is_same_v<T, int>) {
return 0;
} else {
try {} catch (...) {}
return 1;
}
}
};
int main()
{
S<int> s;
return s.foo(); // expect "return 0"
}
GCC says:
error: 'try' in 'constexpr' function
Clang says:
error: statement not allowed in constexpr function
Neither of them seem to notice that the "try" statement is located in a discarded branch of the if constexpr
statement.
If I factor the try
/catch
out into a non-constexpr member function void trycatch()
, then both Clang and GCC are happy with the code again, even though its behavior ought to be equivalent to the unhappy version.
template<class T>
struct S {
void trycatch() {
try {} catch (...) {}
}
constexpr int foo() {
if constexpr (std::is_same_v<T, int>) {
return 0;
} else {
trycatch(); // This is fine.
return 1;
}
}
};
Is this
foo()
?(Irrelevant background: I'm implementing constexpr any::emplace<T>()
for an allocator-aware version of any
whose allocator might be constexpr-per-P0639 (i.e. it might lack a deallocate
member function) or might not. In the former case we don't want or need the try
; in the latter case we need the try
in order to call deallocate
if the constructor of T
throws.)
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.
A constexpr function is one whose return value is computable at compile time when consuming code requires it. Consuming code requires the return value at compile time to initialize a constexpr variable, or to provide a non-type template argument.
A non-static data member cannot be constexpr. static constexpr int x = 5; int y; };
const can only be used with non-static member functions whereas constexpr can be used with member and non-member functions, even with constructors but with condition that argument and return type must be of literal types.
The compilers are obeying the Standard. C++17 draft N4659 says ([dcl.constexpr]/(3.4.4)):
The definition of a constexpr function shall satisfy the following requirements:
...
its function-body shall be
= delete
,= default
, or a compound-statement that does not contain
...
a try-block, or
...
And none of the rules for "discarded statements" such as the else
statement in your S<int>::foo
override this rule. The only special things specified about discarded statements are that discarded statements are not instantiated, odr-uses within discarded statements do not cause a definition for the used declaration to be required, and discarded return
statements are ignored when determining the true return type of a function with placeholder return type.
I did not see any existing C++ Issue discussing this, and the paper P0292R1 which proposed if constexpr
does not address interactions with constexpr functions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With