59

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);
W.H
  • 1,838
  • 10
  • 24

4 Answers4

52

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 ...

Jans
  • 11,064
  • 3
  • 37
  • 45
  • Why exactly is the condition you quoted no longer met if it depends on the template parameter? To my understanding it still holds that "no valid specialization can be generated". – philipp2100 Mar 21 '20 at 18:46
  • Seeing stuff like that makes me think at some point in time we should have just kept to growing corn or something. – Aviv Cohn Jun 29 '20 at 19:21
  • @AvivCohn I know what you mean. Or maybe move further and end up with a safer language? Some think it's Rust... Either way, I hope there will be one. – philipp2100 Jul 04 '20 at 11:44
  • [This answer](https://stackoverflow.com/a/30081493/5452773) claims that it's indeed *not* dependant on the template parameters, because it "does not differ from one instantiation to another", which would mean that cppreference is wrong. – philipp2100 Jul 04 '20 at 11:48
  • 1
    @philipp2100 It's not even about safe vs. unsafe. It's just about a sane solution we can have a good time using. C++ has some beautiful parts, but others are just like... what were they thinking? – Aviv Cohn Jul 07 '20 at 14:08
  • I also just stumbled over the "and the template is not instantiated" part of the quote. It seems that since the template *is* instantiated, the quote doesn't apply here at all. I couldn't find the wording on this in N4606 either, though. It would be great if someone could shed some light on all of this or ask a new question and link it here. Maybe I'll do it. – philipp2100 Jul 14 '20 at 09:40
  • @philipp2100 - variable templates can be explicitly specialized. So a specialization *is possible* where the variable is in fact true. It's a slippery slope, and some don't like it, but it does circumvent the "always ill formed" nature of the condition. – StoryTeller - Unslander Monica Sep 07 '21 at 21:48
  • @StoryTeller-UnslanderMonica I think you're right about that point. The answer I linked to that says "does not differ" refers to a different case where it's indeed never possible that they differ. – philipp2100 Sep 13 '21 at 10:10
  • Because of the limitation "and the template is not instantiated", this paragraph indeed may not apply here but the next but one cited in [this answer](https://stackoverflow.com/questions/30078818/static-assert-dependent-on-non-type-template-parameter-different-behavior-on-gc/30081493#30081493). – philipp2100 Sep 13 '21 at 10:13
  • @philipp2100 - "The next one" mentions lack of dependence on the template parameter as a condition. The construct here is by definition, a dependent value. – StoryTeller - Unslander Monica Sep 13 '21 at 13:38
  • @StoryTeller-UnslanderMonica Yes, I agree with that (if you refer to the construct in this answer, not the question). I just wanted to clarify that IMO it's usually the third bullet point of [temp.res]/8 (`a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter`) that makes the dependence necessary, not the first one quoted in this answer (unless the template is indeed not instantiated). – philipp2100 Sep 23 '21 at 04:35
26

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);
Community
  • 1
  • 1
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • 8
    Ironic really since that's exactly what this gives us! Silly language. – Lightness Races in Orbit Dec 27 '18 at 16:42
  • [https://en.cppreference.com/w/cpp/utility/variant/visit](https://en.cppreference.com/w/cpp/utility/variant/visit) illustrates a different variant: `template inline constexpr bool always_false_v = false;` – Matt Eding Aug 07 '21 at 04:27
7

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 form 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
  }
  
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
4

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;
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142