Почему оператор switch нельзя применять к строкам? - PullRequest
193 голосов
/ 16 марта 2009

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

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

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

Ответы [ 19 ]

168 голосов
/ 16 марта 2009

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

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

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

53 голосов
/ 16 марта 2009

Как упоминалось ранее, компиляторам нравится создавать таблицы поиска, которые оптимизируют операторы switch до почти O (1) синхронизации, когда это возможно. Добавьте к этому тот факт, что язык C ++ не имеет строкового типа - 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 ... забавно, как это происходит.

29 голосов
/ 16 марта 2009

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

#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's 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;
}

Код, написанный Штефаном Раком 25 июля 2001 года.

14 голосов
/ 10 декабря 2014

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

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

Использование static в коде codeguru возможно с поддержкой компилятором списков инициализаторов, что означает 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]);
}
11 голосов
/ 16 марта 2009

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

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

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

9 голосов
/ 12 октября 2017

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
}
8 голосов

std::map + C ++ 11 лямбда-паттернов без перечислений

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

#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: Статические переменные в методах класса , но мы также можем использовать методы, описанные в: Статические конструкторы в C ++? Мне нужно инициализировать частные статические объекты

Необходимо было преобразовать захват лямбда-контекста [&] в аргумент, или это было бы неопределенным: 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;
    }
}
6 голосов
/ 16 марта 2009

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

5 голосов
/ 11 октября 2016

Почему бы и нет? Вы можете использовать реализацию переключателя с эквивалентным синтаксисом и той же семантикой. Язык C вообще не имеет объектов и строковых объектов, но строки в C - строки с нулевым символом в конце, на которые ссылается указатель. Язык C++ имеет возможность создавать функции перегрузки для сравнение объектов или проверка равенства объектов. Поскольку C как C++ достаточно гибок, чтобы иметь такой переключатель для строк для C язык и для объектов любого типа, которые поддерживают сравнение или проверку равенство для 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 или любые структуры или классы, которые поддерживают операции равенства (или комбинации для режима quick ).

Особенности

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

Синтаксические различия с переключением языка

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

Для C++97 языка используется линейный поиск. Для C++11 и более современных можно использовать режим поиска по дереву в режиме quick, в котором оператор return в CASE становится недопустимым. Существует языковая реализация C, в которой используются сравнения типов и строк с нулем в конце.

Читать подробнее о этой реализации переключателя.

4 голосов
/ 16 марта 2009

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

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