Reputation: 1987
I want to raise a compile time error when non of the constexpr if conditions is true eg:
if constexpr(condition1){
...
} else if constexpr (condition2) {
....
} else if constexpr (condition3) {
....
} else {
// I want the else clause never taken. But I heard the code below is not allowed
static_assert(false);
}
// I'd rather not repeat the conditions again like this:
static_assert(condition1 || condition2 || condition3);
Upvotes: 63
Views: 6968
Reputation: 158539
With C++23, the answer to this question changes. With the paper P2593: Allowing static_assert(false), which was accepted at 2023-02 Issaquah meeting, we are now allowed to use the idiom the OP wants to use.
We can see from the accepted wording that dcl.pre p10 was modified so that static_assert
has no effect in template definition context and that temp.res p6 was modified so that it is no longer ill-formed no diagnostic required if a static_assert
fails for all specializations.
It also added the following example:
template <class T>
void f(T t) {
if constexpr (sizeof(T) == sizeof(int)) {
use(t);
} else {
static_assert(false, "must be int-sized");
}
}
void g(char c) {
f(0); // OK
f(c); // error: must be int-sized
}
Upvotes: 14
Reputation: 570
One way to deal with this is to static-assert the last constexpr condition within the last else branch, like this:
if constexpr(condition1) {
...
} else if constexpr(condition2) {
...
} else {
static_assert(condition3);
...
}
This can still be a problem in some cases, but if as in many cases the conditions are mutually exclusive, it should work.
Upvotes: 1
Reputation: 69902
taking a slightly different tack...
#include <ciso646>
template<auto x> void something();
template<class...Conditions>
constexpr int which(Conditions... cond)
{
int sel = 0;
bool found = false;
auto elect = [&found, &sel](auto cond)
{
if (not found)
{
if (cond)
{
found = true;
}
else
{
++sel;
}
}
};
(elect(cond), ...);
if (not found) throw "you have a logic error";
return sel;
}
template<bool condition1, bool condition2, bool condition3>
void foo()
{
auto constexpr sel = which(condition1, condition2, condition3);
switch(sel)
{
case 0:
something<1>();
break;
case 1:
something<2>();
break;
case 2:
something<3>();
break;
}
}
int main()
{
foo<false, true, false>();
// foo<false, false, false>(); // fails to compile
}
As I understand it, which
is evaluated in constexpr context, which means that it is legal unless the program has to follow a code path that is illegal in a constexpr context.
For all expected cases, the throw
path is not taken, so the function is legal. When illegal inputs are provided, we go down the ill-formed path, which causes a compiler error.
I'd be interested to know whether this solution is strictly correct from a language-lawyer perspective.
It works on gcc, clang and MSVC.
...or for fans of obfuscated code...
template<class...Conditions>
constexpr int which(Conditions... cond)
{
auto sel = 0;
((cond or (++sel, false)) or ...) or (throw "program is ill-formed", false);
return sel;
}
Upvotes: 5
Reputation: 11250
You have to make the discarded statement dependent of the template parameters
template <class...> constexpr std::false_type always_false{};
if constexpr(condition1){
...
} else if constexpr (condition2) {
....
} else if constexpr (condition3) {
....
} else {
static_assert(always_false<T>);
}
This is so because
[temp.res]/8 - The program is ill-formed, no diagnostic required, if
no valid specialization can be generated for a template or a substatement of a
constexpr if
statement within a template and the template is not instantiated, or ...
Upvotes: 59
Reputation: 172964
Here's a workaround from cppreference.com, i.e. use a type-dependent expression instead.
Note: the discarded statement can't be ill-formed for every possible specialization:
The common workaround for such a catch-all statement is a type-dependent expression that is always false:
e.g.
template<class T> struct dependent_false : std::false_type {};
then
static_assert(dependent_false<T>::value);
Upvotes: 29