Почему оператор switch не может применяться к строкам?

Компилируя следующий код и получил ошибку type illegal.

int main()
{
    // Compilation error - switch expression of type illegal
    switch(std::string("raj"))
    {
    case"sda":
    }
}

Вы не можете использовать строку ни в switch ни в case. Зачем? Есть ли какое-нибудь решение, которое хорошо работает для поддержки логики, похожей на включение строк?

Ответ 1

Причина, связанная с системой типов. C/С++ не поддерживает строки как тип. Он поддерживает идею постоянного массива char, но на самом деле он не совсем понимает понятие строки.

Чтобы сгенерировать код для оператора switch, компилятор должен понимать, что означает, что для двух значений равны. Для таких элементов, как int и enums, это тривиальное сравнение бит. Но как компилятор сравнивает 2 строковых значения? Нечувствительны к регистру, нечувствительны, осведомлены о культуре и т.д. Без полного понимания строки это невозможно точно ответить.

Кроме того, операторы switch C/С++ обычно генерируются как таблицы ветвей. Не так легко создать таблицу ветвей для переключателя стиля строки.

Ответ 2

Как упоминалось ранее, компиляторы любят строить таблицы поиска, которые оптимизируют операторы switch до ближайшего времени O (1), когда это возможно. Объедините это с тем, что язык С++ не имеет строкового типа. std::string является частью стандартной библиотеки, которая не является частью самого языка.

Я предложу альтернативу, которую вы, возможно, захотите рассмотреть, я использовал ее в прошлом для хорошего эффекта. Вместо переключения самой строки, переключите результат хэш-функции, которая использует строку в качестве входных данных. Ваш код будет почти таким же понятным, как переключение строки, если вы используете предопределенный набор строк:

enum string_code {
    eFred,
    eBarney,
    eWilma,
    eBetty,
    ...
};

string_code hashit (std::string const& inString) {
    if (inString == "Fred") return eFred;
    if (inString == "Barney") return eBarney;
    ...
}

void foo() {
    switch (hashit(stringValue)) {
    case eFred:
        ...
    case eBarney:
        ...
    }
}

Есть куча очевидных оптимизаций, которые в значительной степени следуют тому, что компилятор C будет делать с оператором switch... забавно, как это происходит.

Ответ 3

Вы можете использовать только примитив включения, такой как int, char и перечисление. Самое простое решение сделать это, как вы хотите, это использовать перечисление.

#include <map>
#include <string>
#include <iostream.h>

// Value-Defintions of the different String values
static enum StringValue { evNotDefined,
                          evStringValue1,
                          evStringValue2,
                          evStringValue3,
                          evEnd };

// Map to associate the strings with the enum values
static std::map<std::string, StringValue> s_mapStringValues;

// User input
static char szInput[_MAX_PATH];

// Intialization
static void Initialize();

int main(int argc, char* argv[])
{
  // Init the string map
  Initialize();

  // Loop until the user stops the program
  while(1)
  {
    // Get the user input
    cout << "Please enter a string (end to terminate): ";
    cout.flush();
    cin.getline(szInput, _MAX_PATH);
    // Switch on the value
    switch(s_mapStringValues[szInput])
    {
      case evStringValue1:
        cout << "Detected the first valid string." << endl;
        break;
      case evStringValue2:
        cout << "Detected the second valid string." << endl;
        break;
      case evStringValue3:
        cout << "Detected the third valid string." << endl;
        break;
      case evEnd:
        cout << "Detected program end command. "
             << "Programm will be stopped." << endl;
        return(0);
      default:
        cout << "'" << szInput
             << "' is an invalid string. s_mapStringValues now contains "
             << s_mapStringValues.size()
             << " entries." << endl;
        break;
    }
  }

  return 0;
}

void Initialize()
{
  s_mapStringValues["First Value"] = evStringValue1;
  s_mapStringValues["Second Value"] = evStringValue2;
  s_mapStringValues["Third Value"] = evStringValue3;
  s_mapStringValues["end"] = evEnd;

  cout << "s_mapStringValues contains "
       << s_mapStringValues.size()
       << " entries." << endl;
}

Код, написанный от Stefan Ruck 25 июля 2001 года.

Ответ 4

Обновление С++ 11, по-видимому, не @MarmouCorp выше, но http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htm

Использует две карты для преобразования между строками и перечислением класса (лучше, чем обычное перечисление, поскольку его значения ограничены внутри него и обратный поиск хороших сообщений об ошибках).

Использование статики в кодедегуруре возможно с поддержкой компилятора для списков инициализаторов, что означает VS 2013 plus. gcc 4.8.1 было в порядке, не уверен, насколько дальше он будет совместим.

/// <summary>
/// Enum for String values we want to switch on
/// </summary>
enum class TestType
{
    SetType,
    GetType
};

/// <summary>
/// Map from strings to enum values
/// </summary>
std::map<std::string, TestType> MnCTest::s_mapStringToTestType =
{
    { "setType", TestType::SetType },
    { "getType", TestType::GetType }
};

/// <summary>
/// Map from enum values to strings
/// </summary>
std::map<TestType, std::string> MnCTest::s_mapTestTypeToString
{
    {TestType::SetType, "setType"}, 
    {TestType::GetType, "getType"}, 
};

...

std::string someString = "setType";
TestType testType = s_mapStringToTestType[someString];
switch (testType)
{
    case TestType::SetType:
        break;

    case TestType::GetType:
        break;

    default:
        LogError("Unknown TestType ", s_mapTestTypeToString[testType]);
}

Ответ 5

Проблема заключается в том, что по соображениям оптимизации оператор switch в С++ не работает ни на чем, кроме примитивных типов, и вы можете сравнивать их только с константами времени компиляции.

Предположительно, причина ограничения заключается в том, что компилятор может применить некоторую форму оптимизации, скомпилировав код до одной команды cmp и goto, где адрес вычисляется на основе значения аргумента во время выполнения. Поскольку ветвление и петли не очень хорошо сочетаются с современными процессорами, это может быть важной оптимизацией.

Чтобы обойти это, я боюсь, вам придётся прибегать к заявлениям.

Ответ 6

C++

constexpr хэш-функция:

constexpr unsigned int hash(const char *s, int off = 0) {                        
    return !s[off] ? 5381 : (hash(s, off+1)*33) ^ s[off];                           
}                                                                                

switch( hash(str) ){
case hash("one") : // do something
case hash("two") : // do something
}

Ответ 7

std::map + С++ 11 lambdas pattern без перечислений

unordered_map для потенциального амортизированного O(1): Каков наилучший способ использования HashMap в С++?

#include <functional>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

int main() {
    int result;
    const std::unordered_map<std::string,std::function<void()>> m{
        {"one",   [&](){ result = 1; }},
        {"two",   [&](){ result = 2; }},
        {"three", [&](){ result = 3; }},
    };
    const auto end = m.end();
    std::vector<std::string> strings{"one", "two", "three", "foobar"};
    for (const auto& s : strings) {
        auto it = m.find(s);
        if (it != end) {
            it->second();
        } else {
            result = -1;
        }
        std::cout << s << " " << result << std::endl;
    }
}

Вывод:

one 1
two 2
three 3
foobar -1

Использование внутри методов с помощью static

Чтобы эффективно использовать этот шаблон внутри классов, инициализируйте карту лямбда статически, иначе вы платите O(n) каждый раз, чтобы построить ее с нуля.

Здесь мы можем уйти с инициализацией {} переменной static: Статические переменные в методах класса, но мы могли бы также использовать описанные методы at: статические конструкторы в С++? Мне нужно инициализировать частные статические объекты

Необходимо было преобразовать захват lambda context [&] в аргумент, или это было бы undefined: const static auto lambda, используемое с захватом по ссылке

Пример, который производит тот же вывод, что и выше:

#include <functional>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

class RangeSwitch {
public:
    void method(std::string key, int &result) {
        static const std::unordered_map<std::string,std::function<void(int&)>> m{
            {"one",   [](int& result){ result = 1; }},
            {"two",   [](int& result){ result = 2; }},
            {"three", [](int& result){ result = 3; }},
        };
        static const auto end = m.end();
        auto it = m.find(key);
        if (it != end) {
            it->second(result);
        } else {
            result = -1;
        }
    }
};

int main() {
    RangeSwitch rangeSwitch;
    int result;
    std::vector<std::string> strings{"one", "two", "three", "foobar"};
    for (const auto& s : strings) {
        rangeSwitch.method(s, result);
        std::cout << s << " " << result << std::endl;
    }
}

Ответ 8

В С++ и C коммутаторы работают только с целыми типами. Вместо этого используйте лестницу if else. Очевидно, что С++ реализовал какую-то инструкцию swich для строк - я думаю, никто не думал, что это стоит, и я согласен с ними.

Ответ 9

Почему бы и нет? Вы можете использовать реализацию коммутатора с эквивалентным синтаксисом и той же семантикой. Язык C не имеет объектов и объектов строк вообще, но строки в C являются строками с нулевым завершением, на которые ссылается указатель. Язык C++ имеет возможность выполнять функции перегрузки для сравнение объектов или проверка соответствия объектов. Поскольку C как C++ достаточно гибко, чтобы иметь такой переключатель для строк для C язык и для объектов любого типа, которые поддерживают comparaison или проверку равенство для языка C++. А современные C++11 позволяют использовать этот переключатель реализация достаточно эффективна.

Ваш код будет таким:

std::string name = "Alice";

std::string gender = "boy";
std::string role;

SWITCH(name)
  CASE("Alice")   FALL
  CASE("Carol")   gender = "girl"; FALL
  CASE("Bob")     FALL
  CASE("Dave")    role   = "participant"; BREAK
  CASE("Mallory") FALL
  CASE("Trudy")   role   = "attacker";    BREAK
  CASE("Peggy")   gender = "girl"; FALL
  CASE("Victor")  role   = "verifier";    BREAK
  DEFAULT         role   = "other";
END

// the role will be: "participant"
// the gender will be: "girl"

Можно использовать более сложные типы, например std::pairs, или любые структуры или классы, которые поддерживают операции равенства (или комы для быстрого режима).

Функции

  • любые типы данных, которые поддерживают сравнения или проверяют равенство
  • возможность создания каскадных вложенных статусов коммутаторов.
  • возможность прерывания или падения по операторам case
  • возможность использования выражений case non constatnt
  • можно включить быстрый статический/динамический режим с поиском дерева (для С++ 11)

Различия в Sintax с языковым переключателем

  • заглавные слова
  • нужны скобки для оператора CASE
  • точка с запятой ';' в конце инструкций не допускается
  • двоеточие ':' в операторе CASE не разрешено
  • требуется одно из BREAK или FALL в конце оператора CASE.

Для языка C++97 используется линейный поиск. Для C++11 и более современных можно использовать quick режим поиска wuth tree, где оператор return в CASE становится недопустимым. Реализация языка C существует там, где используется char* тип и сравнение строк с нулевым завершением.

Прочитайте о этой реализации коммутатора.

Ответ 10

Я думаю, причина в том, что в строках C не являются примитивными типами, как сказал tomjen, думайте в строке как массив char, поэтому вы не можете делать такие вещи, как:

switch (char[]) { // ...
switch (int[]) { // ...

Ответ 11

Чтобы добавить вариант с использованием простейшего возможного контейнера (нет необходимости в упорядоченной карте)... Я не стал бы беспокоиться о перечислении - просто поместите определение контейнера непосредственно перед переключателем, чтобы было легко увидеть, какое число представляет в каком случае.

Это выполняет хешированный поиск в unordered_map и использует связанный int для управления оператором switch. Должно быть довольно быстро. Обратите внимание, что вместо [] используется at, так как я сделал этот контейнер const. Использование [] может быть опасным - если строка отсутствует на карте, вы создадите новое отображение и можете получить неопределенные результаты или постоянно растущую карту.

Обратите внимание, что функция at() выдаст исключение, если строка отсутствует на карте. Так что вы можете сначала протестировать, используя count().

const static std::unordered_map<std::string,int> string_to_case{
   {"raj",1},
   {"ben",2}
};
switch(string_to_case.at("raj")) {
  case 1: // this is the "raj" case
       break;
  case 2: // this is the "ben" case
       break;


}

Ниже приводится версия с тестом для неопределенной строки:

const static std::unordered_map<std::string,int> string_to_case{
   {"raj",1},
   {"ben",2}
};
switch(string_to_case.count("raj") ? string_to_case.at("raj") : 0) {
  case 1: // this is the "raj" case
       break;
  case 2: // this is the "ben" case
       break;
  case 0: //this is for the undefined case

}

Ответ 12

В С++ вы можете использовать оператор switch только для int и char

Ответ 13

В С++ строки не являются гражданами первого класса. Строковые операции выполняются через стандартную библиотеку. Думаю, вот в чем причина. Кроме того, С++ использует оптимизацию таблицы ветвей для оптимизации операторов case switch. Посмотрите на ссылку.

http://en.wikipedia.org/wiki/Switch_statement

Ответ 14

Вы не можете использовать строку в case switch. Только разрешены int и char. Вместо этого вы можете попробовать enum для представления строки и использовать ее в блоке case switch, например

enum MyString(raj,taj,aaj);

Используйте его в инструкции swich case.

Ответ 15

Коммутаторы работают только с интегральными типами (int, char, bool и т.д.). Почему бы не использовать карту для сопряжения строки с номером, а затем использовать этот номер с помощью переключателя?

Ответ 16

    cout << "\nEnter word to select your choice\n"; 
    cout << "ex to exit program (0)\n";     
    cout << "m     to set month(1)\n";
    cout << "y     to set year(2)\n";
    cout << "rm     to return the month(4)\n";
    cout << "ry     to return year(5)\n";
    cout << "pc     to print the calendar for a month(6)\n";
    cout << "fdc      to print the first day of the month(1)\n";
    cin >> c;
    cout << endl;
    a = c.compare("ex") ?c.compare("m") ?c.compare("y") ? c.compare("rm")?c.compare("ry") ? c.compare("pc") ? c.compare("fdc") ? 7 : 6 :  5  : 4 : 3 : 2 : 1 : 0;
    switch (a)
    {
        case 0:
            return 1;

        case 1:                   ///m
        {
            cout << "enter month\n";
            cin >> c;
            cout << endl;
            myCalendar.setMonth(c);
            break;
        }
        case 2:
            cout << "Enter year(yyyy)\n";
            cin >> y;
            cout << endl;
            myCalendar.setYear(y);
            break;
        case 3:
             myCalendar.getMonth();
            break;
        case 4:
            myCalendar.getYear();
        case 5:
            cout << "Enter month and year\n";
            cin >> c >> y;
            cout << endl;
            myCalendar.almanaq(c,y);
            break;
        case 6:
            break;

    }

Ответ 17

во многих случаях вы можете запросить дополнительную работу, вытащив из строки строку char и включив ее. может закончиться тем, что нужно сделать вложенный ключ на charat (1), если ваши случаи начинаются с того же значения. любой, кто читает ваш код, по достоинству оценит намек, потому что большинство будет пытаться просто if-else-if

Ответ 18

Более функциональный обход проблемы с коммутатором:

class APIHandlerImpl
{

// define map of "cases"
std::map<string, std::function<void(server*, websocketpp::connection_hdl, string)>> in_events;

public:
    APIHandlerImpl()
    {
        // bind handler method in constructor
        in_events["/hello"] = std::bind(&APIHandlerImpl::handleHello, this, _1, _2, _3);
        in_events["/bye"] = std::bind(&APIHandlerImpl::handleBye, this, _1, _2, _3);
    }

    void onEvent(string event = "/hello", string data = "{}")
    {
        // execute event based on incomming event
        in_events[event](s, hdl, data);
    }

    void APIHandlerImpl::handleHello(server* s, websocketpp::connection_hdl hdl, string data)
    {
        // ...
    }

    void APIHandlerImpl::handleBye(server* s, websocketpp::connection_hdl hdl, string data)
    {
        // ...
    }
}

Ответ 19

Это потому, что С++ превращает ключи в таблицы перехода. Он выполняет тривиальную операцию над входными данными и переходит к соответствующему адресу без сравнения. Поскольку строка не является числом, а массивом чисел, С++ не может создать таблицу перехода из нее.

movf    INDEX,W     ; move the index value into the W (working) register from memory
addwf   PCL,F       ; add it to the program counter. each PIC instruction is one byte
                    ; so there is no need to perform any multiplication. 
                    ; Most architectures will transform the index in some way before 
                    ; adding it to the program counter

table                   ; the branch table begins here with this label
    goto    index_zero  ; each of these goto instructions is an unconditional branch
    goto    index_one   ; of code
    goto    index_two
    goto    index_three

index_zero
    ; code is added here to perform whatever action is required when INDEX = zero
    return

index_one
...

(код из wikipedia https://en.wikipedia.org/wiki/Branch_table)