How do I select a range of values in a switch statement?
Asked Answered
T

19

37

When I try to compile I get this error:

1>------ Build started: Project: snake, Configuration: Debug Win32 ------
1>  exercise.cpp
1>c:\users\robin\documents\visual studio 2010\projects\snake\snake\exercise.cpp(13): error C2059: syntax error : '>='
1>c:\users\robin\documents\visual studio 2010\projects\snake\snake\exercise.cpp(16): error C2059: syntax error : '>='
1>c:\users\robin\documents\visual studio 2010\projects\snake\snake\exercise.cpp(19): error C2059: syntax error : '>='
1>c:\users\robin\documents\visual studio 2010\projects\snake\snake\exercise.cpp(22): error C2059: syntax error : '>='
1>c:\users\robin\documents\visual studio 2010\projects\snake\snake\exercise.cpp(25): error C2059: syntax error : '>'
1>c:\users\robin\documents\visual studio 2010\projects\snake\snake\exercise.cpp(28): error C2059: syntax error : '=='
1>c:\users\robin\documents\visual studio 2010\projects\snake\snake\exercise.cpp(34): warning C4065: switch statement contains 'default' but no 'case' labels
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

Code:

#include <iostream>
using namespace std;

int main(){
    int score;

    //Vraag de score
    cout << "Score:";
    cin >> score;

    //Switch
    switch(score){
        case >= 100:
            cout << "a";
            break;
        case >= 50:
            cout << "b";
            break;
        case >= 25:
            cout << "c";
            break;
        case >= 10:
            cout << "d";
            break;
        case > 0:
            cout << "e";
            break;
        case == 0:
            cout << "f";
            break;
        default:
            cout << "BAD VALUE";
            break;
    }
    cout << endl;
    return 0;
}

How can I fix this problem? It's a console application, Win32 and my IDE is Windows Enterprise C++ 2010.

I'm learning from Beginning C++ Through Game Programming.

Terhune answered 24/2, 2012 at 14:19 Comment(1)
Just don't use switch/case unless it's a perfect fit.Sassaby
S
35

In C++ case labels are constant expressions, not expressions in general. You need a chain of if-then-else statements to do what you are trying to do.

Alternatively, you can enumerate the values in the switch. This runs marginally faster (though it does not matter in cases like yours), but it is considerably less readable:

switch(score) {
    case 0: cout << "f"; break;
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
    case 7:
    case 8:
    case 9:
    case 10: cout << "e"; break;
    case 11:
    case 12:
    case 13:
    case 14:
    case 15:
    case 16:
    case 17:
    case 18:
    case 19:
    case 20:
    case 21:
    case 22:
    case 23:
    case 24:
    case 25: cout << "c"; break;
    // ...and so on, you get the idea...

}
Shaughn answered 24/2, 2012 at 14:21 Comment(3)
This method, while valid, is not a particularly scalable solution (consider larger ranges, or floating point values) and is also prone to human error.Alter
@Alter mah, you are absolutely right. I mentioned the readability, but scalability is another important issue. With only 101 entries it is marginally OK, but once you cross into multiple hundreds, the approach becomes non-practical. As far as FPs go, switch is not even an option.Shaughn
"case labels are constants, not expressions" - they're both: C++11 6.4.2/2 "case constant-expression*: where the *constant-expression shall be a converted constant expression (5.19)".Headstock
D
68

Some compilers support case ranges like case x ... y as an extension to the C++ language.

Example:

#include <iostream>
using namespace std;

int main(){
    int score;

    //Vraag de score
    cout << "Score:";
    cin >> score;

    //Switch
    switch(score){
       case 0:
            cout << "a";
            break;
       case 0 ... 9:
            cout << "b";
            break;
       case 11 ... 24:
            cout << "c";
            break;
       case 25 ... 49:
            cout << "d";
            break;
       case 50 ... 100:
            cout << "e";
            break;         
        default:
            cout << "BAD VALUE";
            break;
    }
    cout << endl;
    return 0;
}

GCC 4.9, Clang 3.5.1 and Intel C/C++ Compiler 13.0.1 seem to support it (tried on http://gcc.godbolt.org/). On the other hand, Visual C++ 19 doesn't (tried on http://webcompiler.cloudapp.net/).

Duress answered 29/5, 2014 at 1:59 Comment(3)
Please note, this is a GCC Extension ( gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/… ) NOT a C++11 thing!Becharm
Also note, that GCC at least requires whitespace around the ... which isn't the case most of the time in C++.Benignity
You could not feed control a range, could you?Visional
S
35

In C++ case labels are constant expressions, not expressions in general. You need a chain of if-then-else statements to do what you are trying to do.

Alternatively, you can enumerate the values in the switch. This runs marginally faster (though it does not matter in cases like yours), but it is considerably less readable:

switch(score) {
    case 0: cout << "f"; break;
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
    case 7:
    case 8:
    case 9:
    case 10: cout << "e"; break;
    case 11:
    case 12:
    case 13:
    case 14:
    case 15:
    case 16:
    case 17:
    case 18:
    case 19:
    case 20:
    case 21:
    case 22:
    case 23:
    case 24:
    case 25: cout << "c"; break;
    // ...and so on, you get the idea...

}
Shaughn answered 24/2, 2012 at 14:21 Comment(3)
This method, while valid, is not a particularly scalable solution (consider larger ranges, or floating point values) and is also prone to human error.Alter
@Alter mah, you are absolutely right. I mentioned the readability, but scalability is another important issue. With only 101 entries it is marginally OK, but once you cross into multiple hundreds, the approach becomes non-practical. As far as FPs go, switch is not even an option.Shaughn
"case labels are constants, not expressions" - they're both: C++11 6.4.2/2 "case constant-expression*: where the *constant-expression shall be a converted constant expression (5.19)".Headstock
A
24

You can fix this problem by using a series of if/else if statements. Switch/case cannot be used like this in C++.

Alter answered 24/2, 2012 at 14:20 Comment(0)
C
12

It can be done using a std::map with switch:

enum Interval {
   One,
   Two,
   Three,
   NotFound };

// [0,10[ is One, [10,30[ is Two, [30,55[ is Three
std::map<int,Interval> imap { 
    { { 0, One }, 
      { 10, Two },
      { 30, Three },
      { 55, NotFound } };
Interval ivalue = NotFound;
auto f = imap.lower_bound( value );
if( f != imap.end() ) ivalue = f->second;
switch( ivalue ) {
    case One : ...
    case Two : ...
    case Three : ...
    default: ...
}
Celloidin answered 17/2, 2016 at 15:4 Comment(2)
We can also get rid of the pesky enum with lambdas: https://mcmap.net/q/169763/-how-do-i-select-a-range-of-values-in-a-switch-statementHokeypokey
This seems a bit over the top for a simple range check.Velarize
M
8

Switch-case is not a great option for testing ranges. The best option is to use several if :

if (score<0) cout << "BAD VALUE";
if (score == 0)  cout << "f";
if (score>0 && score<10) cout << "e";
if (score>=10 && score <25) cout << "d";
if (score>=25 && score <50) cout << "c";
if (score>=50 && score <100) cout << "b";

If running time is an issue, the following solution is faster :

if (score == 0)  cout << "f";
else if (score<10) cout << "e";
else if (score <25) cout << "d";
else if (score <50) cout << "c";
else if (score <100) cout << "b";
else if (score>=100) cout << "a";
else cout << "BAD VALUE";
Martyrize answered 22/11, 2018 at 6:52 Comment(0)
F
5

In C++ a switch statement can only match constant integer values:

switch (i)
{
    case 1:
    //... stuff
    break;
    case 2:
    //... stuff
    break;
    default:
    //... stuff
}
Fogbow answered 24/2, 2012 at 14:21 Comment(6)
So else if statements are the best stuff here?Terhune
Looks that way, although dasblinkenlight has posted another possible solution, and you could probably do some dirty maths to find another way. But if/else if is going to be the most readable.Fogbow
Switch can also match constant character values. e.g. switch (c) {case 'a': ...; break; case 'b': ...; break;} I know they are converted to integers but your answer is misleading to noobs.Bacolod
@Bacolod That is an integer value. I didn't say int. And it seems quite irrelevant to the question here. You are also allowed enumerations.Fogbow
It's simply not true (anymore). See the answer by Ankit Patel.Indigested
@André Looking closer at that, I think Ankit has mistaken the gcc extension (see Janus Troelsen's answer) for a C++11 feature. I don't see anything at all about this in The Standard (N3936 ~= C++14). Although I have a feeling I read a proposal about this at some point, it seems it was rejected.Fogbow
R
5

The standard does not allow for this:

6.4.2 The switch statement [stmt.switch]

[...] Any statement within the switch statement can be labeled with one or more case labels as follows:

case constant-expression :

where the constant-expression shall be an integral constant expression (5.19).

In other words, you can only use case-values that expand into a single, integral, "hard" compile time constant (e.g. case 5+6:, enum {X = 3}; ... case X*X:).

The way around this is to use if-statements. E.g., to replace

switch (x)
case 0..100:

you'd instead

if (x>=0 && x<=100)

.

Rexrexana answered 25/5, 2012 at 8:25 Comment(0)
P
5

There's a GCC extension that does exactly what you want.

Pewter answered 27/9, 2012 at 19:59 Comment(0)
H
4

std::map::upper_bound + C++11 lambdas

https://mcmap.net/q/169763/-how-do-i-select-a-range-of-values-in-a-switch-statement mentioned lower_bound, but we can also get rid of the enum there with lambdas (or inheritance if you don't have it).

#include <functional>
#include <iostream>
#include <map>

int main() {
    std::string ret;
    const std::map<int,std::function<void()>> m{
        {0, [&](){ ret = "too small"; }},
        {2, [&](){ ret = "[0,2)"; }},
        {5, [&](){ ret = "[2,5)"; }},
        {7, [&](){ ret = "[5,7)"; }},
    };
    const auto end = m.end();
    for (auto i = -1; i < 8; ++i) {
        auto it = m.upper_bound(i);
        if (it == end) {
            ret = "too large";
        } else {
            it->second();
        }
        std::cout << i << " " << ret << std::endl;
    }
}

Output:

-1 too small
0 [0,2)
1 [0,2)
2 [2,5)
3 [2,5)
4 [2,5)
5 [5,7)
6 [5,7)
7 too large

Usage inside methods with static

To use this pattern efficiently inside classes, initialize the lambda map statically, or else you pay n log(n) every time to build it from scratch.

Here we can get away with the {} initialization of a static method variable: Static variables in class methods , but we could also use the methods described at: static constructors in C++? I need to initialize private static objects

It was necessary to transform the lambda context capture [&] into an argument, or that would have been undefined: const static auto lambda used with capture by reference

Example that produces the same output as above:

#include <functional>
#include <iostream>
#include <map>
#include <string>

class RangeSwitch {
public:
    void method(int x, std::string &ret) {
        static const std::map<int,std::function<void(std::string&)>> m{
            {0, [](std::string &ret){ ret = "too small"; }},
            {2, [](std::string &ret){ ret = "[0,2)"; }},
            {5, [](std::string &ret){ ret = "[2,5)"; }},
            {7, [](std::string &ret){ ret = "[5,7)"; }},
        };
        static const auto end = m.end();
        auto it = m.upper_bound(x);
        if (it == end) {
            ret = "too large";
        } else {
            it->second(ret);
        }
    }
};

int main() {
    RangeSwitch rangeSwitch;
    std::string ret;
    for (auto i = -1; i < 8; ++i) {
        rangeSwitch.method(i, ret);
        std::cout << i << " " << ret << std::endl;
    }
}
Hokeypokey answered 19/2, 2017 at 19:6 Comment(0)
D
3

I had the same problem with a score based problem and while the " if/elseif "statements were good to use, for intervals i found that the best option (for me at least because i like how it looks and it's easier for me as a beginner to see my mistakes) is " 1 ... 10 ". but don't forget to use a space between the number and the dots or the program will think that your interval is a number and u will get an error "2 many decimal dots...". Hope it helps.

int score;

int main()
{
    cout<<"Enter score"<<endl;
    cin>>score;

  switch(score){
    case 100:
        cout<<"Your score is Perfect"<<endl;
    break;
    case 90 ... 99:
        cout<<"You got A"<<endl;
    break;
    case 80 ... 89:
        cout<<"You got B"<<endl;
        break;
    case 70 ... 79:
        cout<<"You got C"<<endl;
        break;
    case 60 ... 69:
        cout<<"You got D"<<endl;
        break;
    case 50 ... 59:
        cout<<"You got E"<<endl;
        break;
    case 0 ... 49:
        cout<<"You got F"<<endl;}

  }
Deification answered 11/4, 2015 at 11:1 Comment(1)
Note that this is a compiler extension, not standard C++.Hybrid
D
2

That's simply not how switch works. It only takes single values. You'll have to use if-elseif blocks

Defile answered 24/2, 2012 at 14:21 Comment(5)
single value: class Range {...}; ... switch(something) { case Range(0,100): break; }Rexrexana
@phresnel That would be great if c++ worked that way. Too bad it doesn't see https://mcmap.net/q/169763/-how-do-i-select-a-range-of-values-in-a-switch-statement (the selected answer)Defile
Yes, but by your answer, it is possible, as in that example, Range(0,100) is a 'single value'.Rexrexana
@phresnel e.e. "That is simply not how switch works" Plz lrn2c++Defile
a) What do you mean with "e.e."? // b) "That's simply not how XXX works" is not an answer and does not carry any meaning in itself // c) You state "It only takes single values", but that reasoning only forbids stuff like 0..100 or maybe 0||1||2 , but it allows for stuff like my Range(0,100) example, which is wrong // d) "Plz lrn2c++"? Why so angry. You could have just quoted Section 6.4.2 The Switch-Statement to make you answer a real one, which states that [...] the constant-expression shall be an integral constant expression. And besides, there's no "elseif" in C++.Rexrexana
L
2

Something like this?

case 'A'..'Z' where a not in ['I','L','O']:

Unfortunately no compiler I know of implements that particular extension, though GCC has can do ranges as other answers pointed out. For portability you can cut and paste this DWTFYW licensed snippet. If you're using a custom enum you might resort to code generation to make something similar.

#define CASE_NUMBER \
 case'0':case'1':case'2':case'3':case'4':\
 case'5':case'6':case'7':case'8':case'9'
#define CASE_ALPHA_LOWER \
 case'a':case'b':case'c':case'd':\
 case'e':case'f':case'g':case'h':\
 case'i':case'j':case'k':case'l':\
 case'm':case'n':case'o':case'p':\
 case'q':case'r':case's':case't':\
 case'u':case'v':case'w':case'x':\
 case'y':case'z'
#define CASE_ALPHA_UPPER \
 case'A':case'B':case'C':case'D':\
 case'E':case'F':case'G':case'H':\
 case'I':case'J':case'K':case'L':\
 case'M':case'N':case'O':case'P':\
 case'Q':case'R':case'S':case'T':\
 case'U':case'V':case'W':case'X':\
 case'Y':case'Z'
#define CASE_ALPHA CASE_ALPHA_UPPER:CASE_ALPHA_LOWER
#define CASE_ALPHANUM CASE_ALPHA:CASE_NUMBER

If you access to GHCI such as the online version at https://ghc.io/ you might just generate what you need and paste that into a header e.g.

foldl (++) "" ["case" ++ show x ++ ":" | x <- ['A'..'Z'], not $ x `elem` ['I','L','O']]
Limbert answered 5/3, 2016 at 23:22 Comment(0)
K
1

Switch case statements are a substitute for long if statements that compare a variable to several "integral" values ("integral" values are simply values that can be expressed as an integer, such as the value of a char). The condition of a switch statement is a value. The case says that if it has the value of whatever is after that case then do whatever follows the colon. The break is used to break out of the case statements.

Therefore, you cannot use such conditional statements in case.

The selective structure: switch

Kulp answered 24/2, 2012 at 14:37 Comment(0)
H
1

A potentially useful insight is that switch accepts an expression, so you can fold multiple input values down to one switch case. It's a big ugly, but for consideration:

switch (score / 10)
{
  case 10:
    cout << "a";
    break;

  case 9: case 8: case 7: case 6: case 5:
    cout << "b";
    break;

  case 4: case 3:
    cout << "c";
    break;

  case 2:
    if (score >= 25)
    {
        cout << "c";
        break;
    }
    // else fall through...
  case 1:
    cout << "d";
    break;

  case 0:
    cout << (score > 0 ? "e" : "f");
    break;

  default:
    cout << "BAD VALUE";
    break;
}

Of course, you could have divided by 5 and had case 4: (for 20-24) vs case 5: (25-29) rather than an if inside case 2:, but /10 is arguably more intuitive.

Headstock answered 10/6, 2014 at 4:51 Comment(0)
A
1

This is what worked for me. dividing the mark by 10 and then setting case 10 and 9 to display an "A"(this will display a "A" for any value between 90-100. Then case 8 to display "B", then case 7 will display a "C" for the values from 70-79 and so on.

#include <iostream>
using namespace std;

main ()
{
    int mark;
    cout << "enter your mark: ";
    cin >> mark;
    switch (mark/10)
    {
        case 10: case 9: cout << "A"; break;
        case 8: cout << "B"; break;
        case 7: cout << "C"; break;
        case 6: cout << "D"; break;
        case 5: cout << "PASS"; break;
        default: cout << "FAIL"; break;
    }
}
Adebayo answered 10/2, 2015 at 4:8 Comment(0)
R
1

You can do the following:

//summarize the range to one value
If score < 0
    score = -1

switch(score){
case 1:
    //...
    break;
case 2:
    //...
    break;
case -1:    //complete neg. range
    //...
    break;
//...

}

Royal answered 24/9, 2015 at 11:42 Comment(0)
A
1

Here's a way which I hope is expressive and simple to follow.

You may be surprised by how far gcc/clang etc can optimise the code it generates. I would expect it to be at least as efficient as a switch/case.

#include <iostream>

template<class Value>
struct switcher
{
    constexpr switcher(Value const& value) : value_(value) {}
    constexpr switcher(Value const& value, bool enabled) : value_(value), enabled(enabled) {}

    template<class From, class To, class F>
    constexpr auto in_range(From&& from, To&& to, F&& f)
    {
        if (enabled and (from <= value_ and value_ <= to))
        {
            f();
            return switcher(value_, false);
        }
        else {
            return *this;
        }
    };

    template<class F>
    constexpr auto otherwise(F&& f)
    {
        if (enabled)
            f();
    }

    Value const& value_;
    const bool enabled = true;
};

template<class Value>
constexpr auto decision(Value const& value)
{
    return switcher<Value>(value);
}

void test(int x)
{
    decision(x)
            .in_range(0, 10, [&] { std::cout << x << " maps to option A\n"; })
            .in_range(11, 20, [&] { std::cout << x << " maps to option B\n"; })
            .otherwise([&] { std::cout << x << " is not covered\n"; });
}

int main(int argc, char **argv) {

    test(5);
    test(14);
    test(22);
}
Allopathy answered 27/3, 2017 at 21:25 Comment(0)
E
0

I know this is an old questiion, but since switch statements are in fact wrappers around labels, I find goto may be of (good) use here.

    int value = 40;
    if (value < 10) {
        std::cout << "value < 10" << std::endl;
        goto end;
    }
    if (value < 50) {
        std::cout << "value < 50" << std::endl;
        goto end;
    }
    if (value > 30) {
        std::cout << "value > 30" << std::endl;
        goto end;
    }
end:
    // resume

This way, you can omit all the elses and keep it compact. You ought to be careful when using goto though (in general).

Eyeleteer answered 30/1, 2016 at 1:40 Comment(2)
It might be just me, however, i would suggest not to use goto in this case (or in general in c++) because of the inherent danger of the goto. In this particular case, the overall appearance might be neater but it's more difficult for me to see that the ifs are actual else if statement.Nope
Make this a function returning a string. Then you can use return instead of goto.Quoit
E
0

When I was looking into how I could do something like this, I found out there's no native way.

First solution that came into my mind was to just make arrays of callables but I ended up in template hell.

So I ended up with constexpr lookup table.

template<size_t... TSizes>
constexpr std::array<uint8_t, (TSizes + ...)> IndexedRanges {
    []() constexpr {
        std::array<uint8_t, (TSizes + ...)> lResult { };
        size_t lOffset = 0;
        auto lIndex = 0;
        for (const auto lSize : { TSizes... })
        {
            for (size_t i = 0; i < lSize; i++)
                lResult[lOffset + i] = lIndex;
                
            lOffset += lSize;
            ++lIndex;
        }
        return lResult;
    }()
};

You can use it like this:

switch(IndexedRanges<1, 15, 10, 10, 10, 10, 5>[x])
{
    case 1:
        std::cout << "range 1" << std::endl; // 1 - 15
        break;
    case 2:
        std::cout << "range 2" << std::endl; //16 - 25
        break;
    case 3:
        std::cout << "range 3" << std::endl; //26 - 35
        break;
    case 4:
        std::cout << "range 4" << std::endl; //36 - 45
        break;
    case 5:
        std::cout << "range 5" << std::endl; //46 - 55
        break;            
    case 6:
        std::cout << "range 6" << std::endl; //56 - 60
        break;
    case 0:
    default:
        std::cout << "default" << std::endl;
}

I used uint8_t here, but for longer ranges you could make it more generic.

But as you might have noticed it doesn't default on out-of-bound values. So I ended up extending std::array with my own type with get method that returns default option on out-of-bounds.

template<size_t TSize>
class RangeMap : public std::array<size_t, TSize>
{
public:
    constexpr inline uint8_t get(size_t aIndex) const
    {
        return this->data()[std::min(aIndex, this->size() - 1)];
    }
};

So next step was adding the out-of-bounds option to my lookup table and switching to RangeMap

template<size_t... TSizes>
constexpr RangeMap<(TSizes + ...) + 1> IndexedRanges {
    []() constexpr {
        RangeMap<(TSizes + ...) + 1> lResult { };
        size_t lOffset = 0;
        auto lIndex = 0;
        for (const auto lSize : { TSizes... })
        {
            //std::fill(lResult.begin() + lOffset, lResult.begin() + lOffset + lSize, lIndex++); //`constexpr` since C++20
            for (size_t i = 0; i < lSize; i++)
                lResult[lOffset + i] = lIndex;
                
            lOffset += lSize;
            ++lIndex;
        }
        lResult[lOffset] = lIndex; //this line adds extra option for the out-of-bounds
        return lResult;
    }()
};

And then you just add .get to your switch when you want a boundary check.

switch(IndexedRanges<1, 15, 10, 10, 10, 10, 5>.get(x))

This might not solve the problem of branch prediction, but you branch only once. With else-ifs you would branch for every range segment.

Everara answered 23/3, 2023 at 14:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.