开发者

switch/case statement in C++ with a QString type

开发者 https://www.devze.com 2023-02-20 16:24 出处:网络
I want to use switch-case in my program but the compiler gives me this error: switch expression of type \'开发者_StackOverflow社区QString\' is illegal

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

switch expression of type '开发者_StackOverflow社区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 ;
}


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;
}


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.


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


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");


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


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);
}


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);


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" )
{
   // ..
}
// ....


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"
    );
}


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;
}


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 ;

uberswitch( 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 ;
}

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

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


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.


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;
}


It is identical to a C++ switch statement.

switch(var){
  case(option1):
      doesStuff();
      break;
  case(option2):
     etc();
     break;
}
0

精彩评论

暂无评论...
验证码 换一张
取 消