39

I want to use switch-case in my program but the compiler gives me this error:

switch expression of type 'QString' is illegal

How can I use the switch statement with a QString?

My code is as follows:

bool isStopWord( QString word )
{
bool flag = false ;

switch( word )
{
case "the":
    flag = true ;
    break ;
case "at" :
    flag = true ;
    break ;
case "in" :
    flag = true ;
    break ;
case "your":
    flag = true ;
    break ;
case "near":
    flag = true ;
    break ;
case "all":
    flag = true ;
    break ;
case "this":
    flag = true ;
    break ;
}

return flag ;
}
phuclv
  • 37,963
  • 15
  • 156
  • 475
amiref
  • 3,181
  • 7
  • 38
  • 62
  • 3
    Please post the code that's not working, and the _exact_ error message the compiler is giving. We can't guess what you're doing wrong. – Mat Mar 27 '11 at 20:30
  • A `switch` statement "in Qt" is just like any C++ `switch` statement You need to post code which shows the problem you are having. – Håvard S Mar 27 '11 at 20:31
  • @HåvardS except that in Qt there's a special means to switch on strings – phuclv Oct 07 '16 at 03:42
  • For anyone reading this code, it can be obviously turned into one-liner that uses `||`, or less obviously but arguably more clearly a check for containment within a set. –  Jun 14 '21 at 23:42

14 Answers14

36

You can, creating an QStringList before iteration, like this:

QStringList myOptions;
myOptions << "goLogin" << "goAway" << "goRegister";

/*
goLogin = 0
goAway = 1
goRegister = 2
*/

Then:

switch(myOptions.indexOf("goRegister")){
  case 0:
    // go to login...
    break;

  case 1:
    // go away...
    break;

  case 2:
    //Go to Register...
    break;

  default:
    ...
    break;
}
jeromecc
  • 92
  • 1
  • 10
Lauro Oliveira
  • 2,362
  • 1
  • 18
  • 12
  • 8
    The only problem with it is that it is fragile, i.e. overlapping string values. The real solution is proper mapping, although that is not a Qt idea, just general concept. – László Papp Dec 20 '14 at 11:54
  • I so liked the idea, how did I not think of this? thank you. – Beyondo Jun 06 '18 at 21:06
33

How can I use the switch statement with a QString?

You can't. In C++ language switch statement can only be used with integral or enum types. You can formally put an object of class type into a switch statement, but that simply means that the compiler will look for a user-defined conversion to convert it to integral or enum type.

msrd0
  • 7,816
  • 9
  • 47
  • 82
AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • This sounds cool - but not detailed enough for my to do it. Can you provide sample code? – TSG Sep 29 '13 at 18:18
  • 1
    @Michelle: There's no meaningful way to convert a generic string to integer or enum type, which means that there's no meaningful way to use a general `QString` in a `switch` statement. So, my answer is: "it can't be done". In general case you can forget about `switch` statement and use a sequence if `if` statements instead. – AnT stands with Russia Sep 29 '13 at 19:14
  • @Michelle: One example of a specific case when it becomes possible with a `switch` is when your strings come from a restricted and pre-defined table of strings. In that case you can convert your string to its index and use that index as selector value in `switch` statement. That's exactly what olarva's answer is illustrating. – AnT stands with Russia Sep 29 '13 at 19:16
  • Actually, this is possibly by the standard library, not the core language, but C++ means the standard library to many, too. – László Papp Dec 20 '14 at 11:52
  • @AnT there is a meaningful way to convert a generic string to an enum value using [`QMetaEnum`](http://stackoverflow.com/a/25873662/995714), which means a way to switch on a `QString` – phuclv Sep 05 '15 at 16:45
5

It's not possible to switch directly on strings in C++. However it's possible in Qt using QMetaEnum as shown here: Q_ENUM and how to switch on a string. You don't even need C++14 like in Anthony Hilyard's answer, and the matching cases are not the hashes of the strings so there's zero chance of hash collision

Basically QMetaEnum can convert from string to enum value and vice versa so we'll use that to jump to the correct branch. One small limitation is that strings are enum values so the string must be a valid C++ identifier. But that's easy to workaround, just replace the special characters with a specific rule if necessary

To do that, first declare an enum with the strings to be used in switch cases as enumerator name in your class declaration. Then add the enum to the metadata with Q_ENUMS in order for the program to search later.

#include <QMetaEnum>

class TestCase : public QObject
{
    Q_OBJECT
    Q_ENUMS(Cases)        // metadata declaration

    QMetaObject MetaObject;
    QMetaEnum MetaEnum;   // enum metadata

    TestCase() :
    // get information about the enum named "Cases"
    MetaObject(this->staticMetaObject),
    MetaEnum(MetaObject.enumerator(MetaObject.indexOfEnumerator("Cases"))
    {}

public:
    explicit Test(QObject *parent = 0);

    enum Cases
    {
        THE, AT, IN, THIS // ... ==> strings to search, case sensitive
    };

public slots:
    void SwitchString(const QString &word);
};

Then just implement the needed switch inside SwitchString after converting the string to the corresponding value with QMetaEnum::keyToValue.

The comparison is case sensitive so if you want a case insensitive search, convert the input string to upper/lower case first. You can also do other transformations necessary to the string. For example in case you need to switch strings with blank spaces or forbidden characters in C++ identifiers, you may convert/remove/replace those characters to make the string a valid identifier.

void TestCase::SwitchString(const QString &word)
{
    switch (MetaEnum.keyToValue(word.toUpper().toLatin1()))
    // or simply switch (MetaEnum.keyToValue(word)) if no string modification is needed
    {
        case THE:  /* do something */ break;
        case AT:   /* do something */ break;
        case IN:   /* do something */ break;
        case THIS: /* do something */ break;
        default:   /* do something */ break;
    }
}

Then just use the class for switching the strings. For example:

TestCase test;
test.SwitchString("At");
test.SwitchString("the");
test.SwitchString("aBCdxx");
phuclv
  • 37,963
  • 15
  • 156
  • 475
5

@DomTomCat's answer already touched on this, but since the question is specifically asking about Qt, there is a better way.

Qt already has a hashing function for QStrings, but unfortunately Qt4's qHash is not qualified as a constexpr. Luckily Qt is open source, so we can copy the qHash functionality for QStrings into our own constexpr hashing function and use that!

Qt4's qHash source

I've modified it to only need one parameter (string literals are always null-terminated):

uint constexpr qConstHash(const char *string)
{
    uint h = 0;

    while (*string != 0)
    {
        h = (h << 4) + *string++;
        h ^= (h & 0xf0000000) >> 23;
        h &= 0x0fffffff;
    }
    return h;
}

Once you've defined this, you can use it in switch statements like so:

QString string;
// Populate the QString somehow.

switch (qHash(string))
{
    case qConstHash("a"):
        // Do something.
        break;
    case qConstHash("b"):
        // Do something else.
        break;
}

Since this method uses the same code Qt uses to calculate hashes, it will have the same hash collision resistance as QHash, which is generally very good. The downside is that this requires a fairly recent compiler--since it has non-return statements in the constexpr hashing function, it requires C++14.

Anthony Hilyard
  • 1,220
  • 12
  • 27
4

If you can use a modern C++ compiler then you could compute a compile time hash value for your strings. In this answer there's an example of a rather simple constexpr hashing function.

So a solution can look like this:

// function from https://stackoverflow.com/a/2112111/1150303
// (or use some other constexpr hash functions from this thread)
unsigned constexpr const_hash(char const *input) {
    return *input ?
    static_cast<unsigned int>(*input) + 33 * const_hash(input + 1) :
    5381;
}

QString switchStr = "...";
switch(const_hash(switchStr.toStdString().c_str()))
{
case const_hash("Test"):
    qDebug() << "Test triggered";
    break;
case const_hash("asdf"):
    qDebug() << "asdf triggered";
    break;
default:
    qDebug() << "nothing found";
    break;
}

It is still not a perfect solution. There can be hash collisions (hence test your program whenever you add/change case) and you have to be careful in the conversion from QString to char* if you want to use exotic or utf characters, for instance.

For c++ 11 add CONFIG += c++11 to your project, for Qt5. Qt4: QMAKE_CXXFLAGS += -std=c++11

Community
  • 1
  • 1
DomTomCat
  • 8,189
  • 1
  • 49
  • 64
1

try this:

// file qsswitch.h
#ifndef QSSWITCH_H
#define QSSWITCH_H

#define QSSWITCH(__switch_value__, __switch_cases__) do{\
    const QString& ___switch_value___(__switch_value__);\
    {__switch_cases__}\
    }while(0);\

#define QSCASE(__str__, __whattodo__)\
    if(___switch_value___ == __str__)\
    {\
    __whattodo__\
    break;\
    }\

#define QSDEFAULT(__whattodo__)\
    {__whattodo__}\

#endif // QSSWITCH_H

how to use:

#include "qsswitch.h"

QString sW1 = "widget1";
QString sW2 = "widget2";

class WidgetDerived1 : public QWidget
{...};

class WidgetDerived2 : public QWidget
{...};

QWidget* defaultWidget(QWidget* parent)
{
    return new QWidget(...);
}

QWidget* NewWidget(const QString &widgetName, QWidget *parent) const
{
    QSSWITCH(widgetName,
             QSCASE(sW1,
             {
                 return new WidgetDerived1(parent);
             })
             QSCASE(sW2,
             {
                 return new WidgetDerived2(parent);
             })
             QSDEFAULT(
             {
                 return defaultWidget(parent);
             })
             )
}

there is some simple macro magic. after preprocessing this:

QSSWITCH(widgetName,
         QSCASE(sW1,
         {
             return new WidgetDerived1(parent);
         })
         QSCASE(sW2,
         {
             return new WidgetDerived2(parent);
         })
         QSDEFAULT(
         {
             return defaultWidget(parent);
         })
         )

will work like this:

// QSSWITCH
do{
        const QString& ___switch_value___(widgetName);
        // QSCASE 1
        if(___switch_value___ == sW1)
        {
            return new WidgetDerived1(parent);
            break;
        }

        // QSCASE 2
        if(___switch_value___ == sW2)
        {
            return new WidgetDerived2(parent);
            break;
        }

        // QSDEFAULT
        return defaultWidget(parent);
}while(0);
moskk
  • 27
  • 7
  • It would be helpful to add some explanation of the code. – fedorqui Sep 12 '14 at 11:55
  • 2
    IMHO it's still not much clearer compared to a series of if-else – phuclv Sep 18 '14 at 10:43
  • very ugly. And even no else if for second,third and so on .. Even terribly slow. if vs switch case is way slower in execution. It has to compare every single if statement if it resolves to true. So simply please don't use this! – Meister Schnitzel Sep 09 '16 at 08:50
  • Frankly while the intention is good I wouldn't use for such a custom construct even if my life depends on it. The code is slow as @MeisterSchnitzel has pointed out (using `if-else-if` would be much better but it will make the macro even more cucumbersome than it already is) and is not very readable. – rbaleksandar Nov 08 '17 at 12:41
1

As previously noted this is not a Qt problem, switch statements can only use constant expressions, look at the collection classes a QSet is a good solution

void initStopQwords(QSet<QString>& stopSet)
{
    // Ideally you want to read these from a file
    stopSet << "the";
    stopSet << "at";
    ...

}

bool isStopWord(const QSet<QString>& stopSet, const QString& word)
{
    return stopSet.contains(word);
}
Harald Scheirich
  • 9,676
  • 29
  • 53
  • 1
    However if you take it as a Qt problem you can convert strings to enum values to use in switch because there are already support in Qt – phuclv Sep 18 '14 at 10:40
  • 1
    @LưuVĩnhPhúc: this happens when C++ people answer without Qt expertise. – László Papp Dec 20 '14 at 11:52
0

This seems a little saner IMHO.

bool isStopWord( QString w ) {
    return (
        w == "the" ||
        w == "at" ||
        w == "in" ||
        w == "your" ||
        w == "near" ||
        w == "all" ||
        w == "this"
    );
}
weberc2
  • 7,423
  • 4
  • 41
  • 57
0
case "the":
    //^^^ case label must lead to a constant expression

I am not aware of qt, but you can give this a try. You can avoid switch and directly use == for comparison, if QString is no different than a normal std::string.

if( word == "the" )
{
   // ..
}
else if( word == "at" )
{
   // ..
}
// ....
Mahesh
  • 34,573
  • 20
  • 89
  • 115
  • 1
    `"the"` definitely **is** a constant expression. It evaluates to an address of a `char` whose value is `t`. But an address is not an integer. – wilhelmtell Mar 27 '11 at 22:04
0

I would suggest to use if and break. This would make it near to switch case in the computation.

QString a="one"
if (a.contains("one"))
{

   break;
}
if (a.contains("two"))
{

   break;
}
Sherif O.
  • 506
  • 4
  • 15
0

Late to the party, here's a solution I came up with some time ago, which completely abides to the requested syntax and works with c++11 onwards.

#include <uberswitch/uberswitch.hpp>

bool isStopWord( QString word )
{
bool flag = false ;

uswitch( word )
{
ucase ("the"):
    flag = true ;
    break ;
ucase ("at") :
    flag = true ;
    break ;
ucase ("in") :
    flag = true ;
    break ;
ucase ("your"):
    flag = true ;
    break ;
ucase ("near"):
    flag = true ;
    break ;
ucase ("all"):
    flag = true ;
    break ;
ucase ("this"):
    flag = true ;
    break ;
}

return flag ;
}

The only differences to be noticed are the usage of switch in place of switch and ucase instead of case, with the parenthesis around the ucase value (needed, baucause that's a macro).

Here's the code: https://github.com/falemagn/uberswitch

Fabio A.
  • 2,517
  • 26
  • 35
-2

This has nothing to do with Qt, just as it has nothing to do with the colour of your socks.

C++ switch syntax is as follows:

char c = getc();
switch( c ) {
case 'a':
    a();
    break;
case 'b':
    b();
    break;
default:
    neither();
}

If that doesn't help then please list in detail the error message, possible along with the colour of you socks.

Edit: to respond to your reply, you can't use switch with none-integral types. In particular, you can't use class types. Not objects of type QString and not objects of any other type. You can use an if-else if-else construct instead, or you can use runtime or compile time polymorphism, or overloading, or any of the array of alternatives to a switch.

wilhelmtell
  • 57,473
  • 20
  • 96
  • 131
-3

Check out this, it helps me

int main(int, char **)
{
    static const uint red_hash = 30900;
    static const uint green_hash = 7244734;
    static const uint blue_hash = 431029;
  else  
    static const uint red_hash = 112785;  
    static const uint green_hash = 98619139;  
    static const uint blue_hash = 3027034;
  endif

    QTextStream in(stdin), out(stdout);
    out << "Enter color: " << flush;
    const QString color = in.readLine();
    out << "Hash=" << qHash(color) << endl;

    QString answer;
    switch (qHash(color)) {
    case red_hash:
        answer="Chose red";
        break;
    case green_hash:
        answer="Chose green";
        break;
    case blue_hash:
        answer="Chose blue";
        break;
    default:
        answer="Chose something else";
        break;
    }
    out << answer << endl;
}
mortalis
  • 2,060
  • 24
  • 34
-4

It is identical to a C++ switch statement.

switch(var){
  case(option1):
      doesStuff();
      break;
  case(option2):
     etc();
     break;
}
Jake Kalstad
  • 2,045
  • 14
  • 20