XWX
XWX

Reputation: 1987

How to assert that a constexpr if else clause never happen?

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

Answers (5)

Shafik Yaghmour
Shafik Yaghmour

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

Haim
Haim

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

Richard Hodges
Richard Hodges

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

Jans
Jans

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

songyuanyao
songyuanyao

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

Related Questions