switch 문을 문자열에 적용 할 수없는 이유는 무엇입니까?


227

다음 코드를 컴파일하고의 오류가 발생했습니다 type illegal.

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

switch또는 에서 문자열을 사용할 수 없습니다 case. 왜? 문자열 켜기와 유사한 논리를 지원하기 위해 잘 작동하는 솔루션이 있습니까?


6
MACRO 뒤에 열거 된 맵 구성을 숨기는 부스트 대안이 있습니까?
balki

@balki 나는 부스트에 대해 확실하지 않지만 그러한 매크로를 작성하는 것은 쉽습니다. Qt의 경우 다음 과 같이 매핑을 숨길 수 있습니다.QMetaEnum
phuclv

답변:


189

타입 시스템과 관련이있는 이유. C / C ++는 실제로 문자열을 유형으로 지원하지 않습니다. 상수 char 배열의 아이디어를 지원하지만 실제로 문자열의 개념을 완전히 이해하지는 못합니다.

switch 문에 대한 코드를 생성하려면 컴파일러는 두 값이 동일하다는 의미를 이해해야합니다. 정수 및 열거 형과 같은 항목의 경우 이는 간단한 비트 비교입니다. 그러나 컴파일러는 2 개의 문자열 값을 어떻게 비교해야합니까? 대소 문자 구분, 둔감, 문화 인식 등 ... 문자열을 완전히 인식하지 못하면 정확하게 대답 할 수 없습니다.

또한 C / C ++ 스위치 문은 일반적으로 분기 테이블 로 생성됩니다 . 문자열 스타일 스위치에 대한 분기 테이블을 생성하는 것은 쉽지 않습니다.


11
분기 테이블 인수는 적용되지 않아야합니다. 이는 컴파일러 작성자가 사용할 수있는 유일한 방법입니다. 프로덕션 컴파일러의 경우 스위치의 복잡성에 따라 여러 가지 접근 방식을 자주 사용해야합니다.
plinth

5
@ plinth, 나는 역사적 이유로 주로 거기에 넣었습니다. "C / C ++가 왜 그렇게 하는가"에 대한 많은 질문들이 컴파일러의 역사에 의해 쉽게 대답 될 수 있습니다. 그들이 그것을 썼을 때, C는 영광을 불러 일으켰고 따라서 스위치는 실제로 편리한 브랜치 테이블이었습니다.
JaredPar

114
컴파일러가 if 문에서 2 개의 문자열 값을 비교하는 방법을 알지 못하지만 switch 문에서 동일한 작업을 수행하는 방법을 잊어 버릴 수 있기 때문에 투표하지 않았습니다.

15
첫 두 단락이 유효한 이유라고 생각하지 않습니다. 특히 std::string리터럴이 추가 된 C ++ 14부터 . 대부분 역사적입니다. 그러나 염두에 두어야 할 한 가지 문제 switch는 현재 작동 하는 방식으로 컴파일시 중복 cases 가 감지 되어야한다는 것입니다. 그러나 이것은 문자열에 쉽지 않을 수도 있습니다 (런타임 로케일 선택 등을 고려할 때). 나는 그런 일이 constexpr케이스 를 요구 하거나 지정되지 않은 행동을 추가 해야한다고 가정합니다 (우리가하고 싶지 않은 일).
MM

8
std::string값 을 비교 하거나 std::stringconst char 배열과 연산자 를 비교하는 방법에 대한 명확한 정의가 있습니다 (즉 operator == 사용) 컴파일러가 해당 연산자를 제공하는 모든 유형에 대해 switch 문을 생성하지 못하게하는 기술적 인 이유는 없습니다. 그것은 lables의 수명과 같은 것들에 대한 몇 가지 질문을 열 것이지만,이 모든 것의 전부는 기술적 인 어려움이 아니라 주로 언어 디자인 결정입니다.
MikeMB

60

앞에서 언급했듯이 컴파일러는 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 문을 사용하여 수행하는 작업을 거의 따르는 많은 명백한 최적화가 있습니다.


15
실제로 해싱하지 않기 때문에 이것은 정말 실망입니다. 최신 C ++에서는 constexpr 해시 함수를 사용하여 컴파일 타임에 실제로 해시 할 수 있습니다. 당신의 솔루션은 깨끗해 보이지만 불행히도 사다리가 계속되면 불쾌합니다. 아래의 맵 솔루션이 더 좋으며 함수 호출도 피하십시오. 또한 두 개의 맵을 사용하여 오류 로깅을위한 텍스트를 내장 할 수 있습니다.
Dirk Bester


constexpr 함수가 될 수 있습니까? std :: string 대신 const char *를 전달한다고 가정하십시오.
Victor Stone

그런데 왜? 스위치 위에서 if 문을 항상 사용할 수 있습니다. 둘 다 최소한의 영향을 미치지 만 if-else 조회에서는 스위치의 성능 이점이 사라집니다. if-else를 사용하는 것이 조금 더 빠르지 만 더 중요하게는 더 짧아야합니다.
Zoe

20

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
}

1
사례 중 어느 것도 동일한 값으로 해시되지 않도록해야합니다. 심지어 해시 (예 : hash ( "one")와 같은 값으로 해시하는 다른 문자열이 스위치의 첫 번째 "무언가"를 잘못 수행하는 실수가있을 수 있습니다.
David Ljung Madison Stellar

알고 있지만, 동일한 값으로 해시하면 컴파일되지 않으며 제 시간에 알 수 있습니다.
Nick

좋은 지적이지만 스위치의 일부가 아닌 다른 문자열에 대한 해시 충돌을 해결하지 못합니다. 중요하지 않은 경우도 있지만 이것이 일반적인 "go-to"솔루션 인 경우 보안 문제 등이 될 수 있다고 생각할 수 있습니다.
David Ljung Madison Stellar

7
operator ""코드를 더 아름답게 만들기 위해를 추가 할 수 있습니다 . constexpr inline unsigned int operator "" _(char const * p, size_t) { return hash(p); }그리고 case "Peter"_: break; Demo
hare1039

15

위의 @MarmouCorp가 아닌 http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htm 의 C ++ 11 업데이트

두 개의 맵을 사용하여 문자열과 클래스 열거 형 사이를 변환합니다 (값이 범위 내에 있기 때문에 일반 열거 형보다 낫고 오류 메시지가 좋은 경우 역방향 조회).

codeguru 코드에서 static을 사용하는 것은 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]);
}

나중에 문자열 리터럴과 컴파일 시간 계산 (필자는 C ++ 14 또는 17)이 필요한 솔루션을 찾았습니다. 여기서 컴파일 할 때 케이스 문자열을 해시하고 런타임에 스위치 문자열을 해시 할 수 있습니다. 아마도 실제로는 긴 스위치에 대해서는 가치가 있지만, 중요하다면 이전 버전과의 호환성이 떨어질 것입니다.
Dirk Bester

여기에서 컴파일 타임 솔루션을 공유 할 수 있습니까? 감사!
qed

12

문제는 최적화의 이유로 C ++의 switch 문이 기본 유형 이외의 다른 유형에서는 작동하지 않으며 컴파일 시간 상수와 만 비교할 수 있다는 것입니다.

아마도 제한 이유는 컴파일러가 코드를 컴파일하는 일부 최적화 형식을 하나의 cmp 명령어와 런타임시 인수 값을 기반으로 주소가 계산되는 goto로 적용 할 수 있기 때문입니다. 분기 및 루프는 최신 CPU에서 잘 작동하지 않기 때문에 중요한 최적화가 될 수 있습니다.

이 문제를 해결하려면 if 문에 의지해야합니다.


문자열로 작업 할 수있는 최적화 된 버전의 switch 문이 가능합니다. 그들이 프리미티브 유형에 사용하는 것과 동일한 코드 경로를 재사용 할 수 없다고해서 std::string언어를 처음 사용할 수 없으며 다른 사람들이 효율적인 알고리즘으로 switch 문에서 지원할 수 없다는 의미는 아닙니다 .
ceztko

10

std::map 열거 형이없는 +++++ 람다 패턴

unordered_map할부 상환 가능성 O(1): C ++에서 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메소드 변수 의 초기화를 피할 수 있습니다 : 클래스 methods의 정적 변수 . 그러나 C ++의 정적 생성자에 설명 된 메소드를 사용할 수도 있습니다 . 개인 정적 객체를 초기화해야합니다

람다 컨텍스트 캡처 [&]를 인수로 변환해야 하거나 정의되지 않았을 수 있습니다. const 정적 자동 람다 참조로 캡처와 함께 사용

위와 동일한 출력을 생성하는 예 :

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

3
이 접근 방식과 switch설명 에는 차이가 있습니다. switch명령문 에서 대소 문자 값이 중복 되면 컴파일 시간이 실패합니다. 를 사용하면 std::unordered_map중복 값이 ​​자동으로 적용됩니다.
D.Shawley

6

C ++ 및 C 스위치에서는 정수 유형에서만 작동합니다. 대신 else 사다리를 사용하십시오. C ++은 분명히 문자열에 대해 일종의 swich 문을 구현했을 수 있습니다. 아무도 가치가 없다고 생각하고 동의합니다.


동의하지만이 기능을 사용할 수없는 이유를 알고 계십니까
yesraaj

역사? 실수, 포인터 및 구조체 (C의 다른 데이터 유형 만)를 전환해도 산만하지 않으므로 C는 정수로 제한했습니다.

특히 암시 적 변환을 허용하는 클래스를 켜면 정말 좋은 시간을 가질 수 있습니다.
sharptooth

6

왜 안돼? 동등한 구문과 동일한 의미로 스위치 구현 을 사용할 수 있습니다 . C언어는 전혀 객체와 문자열 개체가 없지만, 문자열의 C널 (null) 포인터에 의해 참조 문자열을 종료했습니다. 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등식 연산을 지원하는 구조체 나 클래스 (또는 빠른 모드의 경우 comarision)와 같이보다 복잡한 유형을 사용할 수 있습니다 .

풍모

  • 비교 또는 평등 확인을 지원하는 모든 유형의 데이터
  • 계단식 중첩 스위치 상태를 구축 할 수 있습니다.
  • 사례 진술을 위반하거나 넘어 질 가능성
  • 제약이없는 사례 표현식을 사용할 가능성
  • 트리 검색으로 빠른 정적 / 동적 모드를 활성화 할 수 있습니다 (C ++ 11의 경우).

언어 전환과의 차이는

  • 대문자 키워드
  • CASE 문에 괄호가 필요합니다
  • 세미콜론 ';' 진술의 끝에는 허용되지 않습니다
  • CASE 문의 콜론 ':'은 허용되지 않습니다.
  • CASE 문의 끝에 BREAK 또는 FALL 키워드 중 하나가 필요합니다.

를 들어 C++97언어 선형 검색을 사용했다. 들어 C++11와 사용 가능한 더 현대적인 quick곳 모드 wuth 트리 검색 반환 되는 경우에는 문이 없다 허용했다. C경우 언어 구현이 존재 char*유형과 제로 종료 문자열 comparisions 사용됩니다.

이 스위치 구현 에 대해 자세히 알아 보십시오 .


6

가능한 가장 간단한 컨테이너를 사용하여 변형을 추가하려면 (주문 맵 필요 없음) 열거 형을 사용하지 않아도됩니다. 컨테이너 정의를 스위치 바로 앞에두면 어떤 숫자가 나타내는 지 쉽게 알 수 있습니다 어떤 경우.

이것은에서 해시 조회를 수행 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}
};
// in C++20, you can replace .count with .contains
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

}

4

tomjen이 말했듯이 C 문자열에서 기본 유형이 아니기 때문에 문자열에서 char 배열로 생각하므로 다음과 같은 작업을 수행 할 수 없습니다.

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

3
문자 배열을 찾지 않으면 문자 배열이 char *로 퇴화 될 가능성이 있으며, 이는 정수형으로 직접 변환됩니다. 따라서 컴파일이 잘 될 수 있지만 확실히 원하는 것을 수행하지는 않습니다.
David Thornley

3

C ++에서 문자열은 일등 시민이 아닙니다. 문자열 작업은 표준 라이브러리를 통해 수행됩니다. 나는 그것이 이유라고 생각합니다. 또한 C ++은 분기 테이블 최적화를 사용하여 스위치 사례 문을 최적화합니다. 링크를보십시오.

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


2

C ++에서는 int 및 char에서만 switch 문을 사용할 수 있습니다


3
문자도 정수로 바뀝니다.
strager

포인터도 가능합니다. 즉, 때로는 다른 언어로 이해되는 것을 컴파일 할 수 있지만 제대로 실행되지는 않습니다.
David Thornley

당신은 실제로 사용 long하고 long long있는 하지 않습니다 로 전환 int. 잘림의 위험이 없습니다.
MSalters


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

    }

4
이 코드는 질문에 대답 할 수 있지만,이 코드가 질문에 응답하는 이유 및 / 또는 방법 에 대한 추가 컨텍스트를 제공 하면 장기적인 가치가 향상됩니다.
Benjamin W.

0

대부분의 경우 문자열에서 첫 번째 문자를 가져 와서 켜면 추가 작업을 열망 할 수 있습니다. 케이스가 동일한 값으로 시작하면 charat (1)에서 중첩 스위치를 수행해야 할 수도 있습니다. 코드를 읽는 사람이라면 누구나 힌트를 얻을 것입니다. 대부분의 경우 if-else-if


0

스위치 문제에 대한 추가 기능 해결 방법 :

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

-1

스위치 케이스에는 문자열을 사용할 수 없으며 int & char 만 허용됩니다. 대신 문자열을 나타 내기 위해 열거 형을 시도하고 스위치 케이스 블록에서 사용할 수 있습니다

enum MyString(raj,taj,aaj);

Swich Case Statement에 사용하십시오.



-1

스위치는 정수 유형 (int, char, bool 등)에서만 작동합니다. 지도를 사용하여 문자열을 숫자와 쌍으로 묶은 다음 해당 숫자를 스위치와 함께 사용하지 않겠습니까?


-2

C ++이 스위치를 점프 테이블로 전환하기 때문입니다. 입력 데이터에 대한 간단한 작업을 수행하고 비교하지 않고 적절한 주소로 이동합니다. 문자열은 숫자가 아니라 숫자의 배열이므로 C ++은 점프 테이블을 만들 수 없습니다.

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의 코드 )


4
C ++에는 구문의 특정 구현이 필요하지 않습니다. 순진한 cmp/ jcc구현은 C ++ 표준에 따라 유효 할 수 있습니다.
Ruslan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.