Mengapa pernyataan beralih tidak dapat diterapkan pada string?

227

Kompilasi kode berikut dan dapatkan kesalahan dari type illegal.

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

Anda tidak dapat menggunakan string di salah satu switchatau case. Mengapa? Apakah ada solusi yang berfungsi baik untuk mendukung logika yang mirip dengan mengaktifkan string?

yesraaj
sumber
6
Apakah ada alternatif dorongan yang menyembunyikan konstruksi peta, di belakang MACRO?
balki
@ Balki Saya tidak yakin tentang peningkatan tetapi mudah untuk menulis makro seperti itu. Dalam kasus Qt maka Anda dapat menyembunyikan pemetaan denganQMetaEnum
phuclv

Jawaban:

189

Alasan mengapa ada hubungannya dengan sistem tipe. C / C ++ tidak benar-benar mendukung string sebagai tipe. Itu mendukung gagasan array char konstan tetapi tidak benar-benar sepenuhnya memahami gagasan string.

Untuk menghasilkan kode pernyataan switch, kompiler harus memahami apa artinya dua nilai menjadi sama. Untuk item seperti int dan enum, ini adalah perbandingan bit sepele. Tetapi bagaimana seharusnya kompiler membandingkan 2 nilai string? Peka huruf besar-kecil, tidak peka, sadar budaya, dll ... Tanpa kesadaran penuh akan string, ini tidak dapat dijawab dengan akurat.

Selain itu, pernyataan switch C / C ++ biasanya dihasilkan sebagai tabel cabang . Tidak mudah menghasilkan tabel cabang untuk sakelar gaya string.

JaredPar
sumber
11
Argumen tabel cabang seharusnya tidak berlaku - itu hanya satu pendekatan yang mungkin tersedia untuk penulis kompiler. Untuk kompiler produksi, kita harus sering menggunakan beberapa pendekatan tergantung pada kompleksitas switch.
alas
5
@plinth, saya letakkan di sana sebagian besar karena alasan historis. Banyak pertanyaan "mengapa C / C ++ melakukan ini" dapat dengan mudah dijawab oleh sejarah kompiler. Pada saat mereka menulisnya, C dimuliakan perakitan dan karenanya beralih benar-benar meja cabang yang nyaman.
JaredPar
114
Saya memilih karena saya tidak mengerti bagaimana bisa kompiler tahu bagaimana membandingkan 2 nilai string dalam pernyataan if tetapi lupa cara untuk melakukan hal yang sama dalam pernyataan switch.
15
Saya tidak berpikir 2 paragraf pertama adalah alasan yang valid. Terutama sejak C ++ 14 ketika std::stringliteral ditambahkan. Sebagian besar sejarah. Tapi satu masalah yang datang ke pikiran adalah bahwa dengan cara switchbekerja saat ini, menduplikasi cases harus dideteksi pada saat kompilasi; namun ini mungkin tidak mudah untuk string (mempertimbangkan pemilihan lokal run-time dan sebagainya). Saya kira hal seperti itu harus memerlukan constexprkasus, atau menambahkan perilaku yang tidak ditentukan (tidak pernah hal yang ingin kita lakukan).
MM
8
Ada definisi yang jelas tentang bagaimana membandingkan dua std::stringnilai atau bahkan std::stringdengan array char char (yaitu dengan menggunakan operator ==) tidak ada alasan teknis yang akan mencegah kompiler membuat pernyataan switch untuk semua jenis yang menyediakan operator itu. Ini akan membuka beberapa pertanyaan tentang hal-hal seperti seumur hidup dari lables tetapi semua dalam semua ini terutama merupakan keputusan desain bahasa, bukan kesulitan teknis.
MikeMB
60

Seperti disebutkan sebelumnya, kompiler suka membuat tabel pencarian yang mengoptimalkan switchpernyataan mendekati waktu O (1) bila memungkinkan. Gabungkan ini dengan fakta bahwa Bahasa C ++ tidak memiliki tipe string -std::string adalah bagian dari Perpustakaan Standar yang bukan merupakan bagian dari Bahasa itu sendiri.

Saya akan menawarkan alternatif yang mungkin ingin Anda pertimbangkan, saya sudah menggunakannya di masa lalu untuk efek yang baik. Alih-alih beralih di atas string itu sendiri, alihkan hasil dari fungsi hash yang menggunakan string sebagai input. Kode Anda akan hampir sejelas peralihan string jika Anda menggunakan serangkaian string yang telah ditentukan:

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:
        ...
    }
}

Ada banyak optimasi jelas yang cukup banyak mengikuti apa yang akan dilakukan oleh kompiler C dengan pernyataan switch ... lucu bagaimana itu terjadi.

D.Shawley
sumber
15
Ini benar-benar mengecewakan karena Anda sebenarnya tidak hashing. Dengan C ++ modern Anda sebenarnya dapat melakukan hash pada waktu kompilasi menggunakan fungsi hash constexpr. Solusi Anda terlihat bersih tetapi memiliki semua yang buruk jika tangga terjadi sayangnya. Solusi peta di bawah ini akan lebih baik dan menghindari pemanggilan fungsi juga. Selain itu dengan menggunakan dua peta Anda bisa membuat teks untuk kesalahan log.
Dirk Bester
Anda juga dapat menghindari enum dengan lambdas: stackoverflow.com/a/42462552/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Mungkinkah hashit menjadi fungsi constexpr? Mengingat bahwa Anda meneruskan di const char * daripada std :: string.
Victor Stone
Tapi kenapa? Anda selalu menggunakan eksekusi pernyataan if di atas sakelar. Keduanya memiliki dampak minimal, tetapi keuntungan kinerja dengan sakelar terhapus oleh pencarian if-else. Hanya menggunakan if-else seharusnya sedikit lebih cepat, tetapi yang lebih penting, jauh lebih pendek.
Zoe
20

C ++

Fungsi hash 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
}
Nick
sumber
1
Anda harus memastikan bahwa tidak ada kasing yang memiliki nilai yang sama. Dan bahkan kemudian, Anda mungkin memiliki beberapa kesalahan di mana string lain yang hash, misalnya, nilai yang sama dengan hash ("satu") akan salah melakukan "sesuatu" pertama di saklar Anda.
David Ljung Madison Stellar
Saya tahu, tetapi jika hash memiliki nilai yang sama tidak akan dikompilasi dan Anda akan melihatnya tepat waktu.
Nick
Poin bagus - tetapi itu tidak menyelesaikan tabrakan hash untuk string lain yang bukan bagian dari switch Anda. Dalam beberapa kasus itu mungkin tidak masalah, tetapi jika ini adalah solusi "masuk" generik, saya bisa membayangkannya sebagai masalah keamanan atau sejenisnya pada beberapa titik.
David Ljung Madison Stellar
7
Anda dapat menambahkan operator ""untuk membuat kode lebih indah. constexpr inline unsigned int operator "" _(char const * p, size_t) { return hash(p); }Dan gunakan seperti case "Peter"_: break; Demo
hare1039
15

Pembaruan C ++ 11 dari tampaknya bukan @MarmouCorp di atas tetapi http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htm

Menggunakan dua peta untuk mengkonversi antara string dan enum kelas (lebih baik daripada enum polos karena nilainya dicakup di dalamnya, dan membalikkan pencarian untuk pesan kesalahan yang bagus).

Penggunaan statis dalam kode codeguru dimungkinkan dengan dukungan kompiler untuk daftar penginisialisasi yang berarti VS 2013 plus. gcc 4.8.1 ok dengan itu, tidak yakin berapa jauh itu akan kompatibel.

/// <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]);
}
Dirk Bester
sumber
Saya harus mencatat bahwa saya kemudian menemukan solusi yang membutuhkan string literal dan waktu perhitungan kompilasi (C ++ 14 atau 17 saya pikir) di mana Anda dapat hash string case pada waktu kompilasi dan hash switch string saat runtime. Akan bermanfaat untuk switch yang sangat lama mungkin tetapi tentu saja bahkan kurang kompatibel jika itu penting.
Dirk Bester
Bisakah Anda membagikan solusi waktu kompilasi di sini? Terima kasih!
qed
12

Masalahnya adalah bahwa untuk alasan optimasi, pernyataan switch di C ++ tidak berfungsi pada apa pun kecuali tipe primitif, dan Anda hanya dapat membandingkannya dengan konstanta waktu kompilasi.

Agaknya alasan pembatasan ini adalah bahwa kompiler dapat menerapkan beberapa bentuk optimisasi yang mengkompilasi kode ke satu instruksi cmp dan kebagian mana alamat tersebut dihitung berdasarkan nilai argumen saat runtime. Karena bercabang dan dan loop tidak bermain dengan baik dengan CPU modern, ini bisa menjadi optimasi penting.

Untuk menyiasatinya, saya khawatir Anda harus menggunakan pernyataan if.

Tomjen
sumber
Versi pernyataan peralihan yang dioptimalkan yang dapat bekerja dengan string jelas dimungkinkan. Fakta bahwa mereka tidak dapat menggunakan kembali jalur kode yang sama yang mereka gunakan untuk tipe primitif tidak berarti bahwa mereka tidak dapat membuat std::stringdan orang lain menjadi warga negara pertama dalam bahasa tersebut dan mendukung mereka dalam pergantian pernyataan dengan algoritma yang efisien.
ceztko
10

std::map + C ++ 11 pola lambdas tanpa enum

unordered_mapuntuk potensi diamortisasi O(1): Apa cara terbaik untuk menggunakan HashMap di 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;
    }
}

Keluaran:

one 1
two 2
three 3
foobar -1

Penggunaan metode dalam dengan static

Untuk menggunakan pola ini secara efisien di dalam kelas, inisialisasi peta lambda secara statis, atau Anda membayar O(n) setiap waktu untuk membangunnya dari awal.

Di sini kita bisa lolos dengan {}inisialisasi staticvariabel metode: Variabel statis dalam metode kelas , tetapi kita juga bisa menggunakan metode yang dijelaskan di: konstruktor statis di C ++? Saya perlu menginisialisasi objek statis pribadi

Itu perlu untuk mengubah penangkapan konteks lambda [&]menjadi argumen, atau yang tidak akan ditentukan: const static auto lambda digunakan dengan penangkapan dengan referensi

Contoh yang menghasilkan output yang sama seperti di atas:

#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;
    }
}
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
3
Perhatikan bahwa ada perbedaan antara pendekatan ini dan switchpernyataan. Duplikasi nilai kasus dalam switchpernyataan adalah kegagalan waktu kompilasi. Menggunakan std::unordered_mapdiam-diam menerima nilai duplikat.
D.Shawley
6

Dalam C ++ dan C switch hanya berfungsi pada tipe integer. Gunakan tangga yang lain sebagai gantinya. C ++ jelas bisa menerapkan semacam pernyataan swich untuk string - saya kira tidak ada yang menganggapnya berharga, dan saya setuju dengan mereka.


sumber
setuju, tetapi apakah Anda tahu apa yang membuat ini tidak mungkin untuk digunakan
yesraaj
Sejarah? Mengaktifkan bilangan real, pointer dan struct (C hanya tipe data lainnya) tidak membuat sanse, jadi C membatasinya ke integer.
Terutama jika Anda mengaktifkan kelas yang memungkinkan konversi tersirat Anda akan memiliki waktu yang sangat baik sekali.
sharptooth
6

Kenapa tidak? Anda dapat menggunakan implementasi sakelar dengan sintaks yang setara dan semantik yang sama. The Cbahasa tidak memiliki objek dan string objek sama sekali, tapi string di Cnull dihentikan string direferensikan oleh pointer. The C++bahasa memiliki kemungkinan untuk membuat fungsi yang berlebihan untuk benda perbandingan atau memeriksa benda kesetaraan. Seperti Cyang C++cukup fleksibel untuk memiliki saklar tersebut untuk string untuk C bahasa dan untuk objek dari jenis apa pun bahwa dukungan comparaison atau cek kesetaraan untuk C++bahasa. Dan modern C++11memungkinkan implementasi switch ini cukup efektif.

Kode Anda akan seperti ini:

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"

Dimungkinkan untuk menggunakan tipe yang lebih rumit misalnya std::pairsatau struct atau kelas apa saja yang mendukung operasi kesetaraan (atau komunikasi untuk cepat mode ).

fitur

  • semua jenis data yang mendukung perbandingan atau memeriksa kesetaraan
  • kemungkinan untuk membangun cascading nested switch statemens.
  • kemungkinan untuk menembus atau jatuh melalui pernyataan kasus
  • kemungkinan untuk menggunakan ekspresi case non constatnt
  • mungkin untuk mengaktifkan mode statis / dinamis cepat dengan pencarian pohon (untuk C ++ 11)

Perbedaan sintax dengan saklar bahasa adalah

  • kata kunci huruf besar
  • membutuhkan tanda kurung untuk pernyataan KASUS
  • titik koma ';' di akhir pernyataan tidak diizinkan
  • titik dua ':' di pernyataan CASE tidak diperbolehkan
  • memerlukan salah satu kata kunci BREAK atau FALL di akhir pernyataan CASE

Untuk C++97bahasa digunakan pencarian linier. Untuk C++11dan yang lebih modern, gunakan quickmode pencarian pohon di mana pernyataan kembali dalam KASUS tidak diizinkan. The Cpelaksanaan bahasa ada di manachar* jenis dan nol-dihentikan comparisions string digunakan.

Baca lebih lanjut tentang implementasi sakelar ini.

oklas
sumber
6

Untuk menambahkan variasi menggunakan wadah sesederhana mungkin (tidak perlu peta yang dipesan) ... Saya tidak akan repot dengan enum - cukup masukkan definisi wadah segera sebelum beralih sehingga akan mudah untuk melihat nomor yang diwakilkan kasus yang mana.

Ini melakukan pencarian hash di unordered_mapdan menggunakan yang terkait intuntuk mengarahkan pernyataan switch. Seharusnya cukup cepat. Catatan yang atdigunakan sebagai ganti [], karena saya sudah membuat wadah itu const. Menggunakan[] bisa berbahaya - jika string tidak ada di peta, Anda akan membuat pemetaan baru dan mungkin berakhir dengan hasil yang tidak ditentukan atau peta yang terus tumbuh.

Perhatikan bahwa at()fungsi akan mengeluarkan pengecualian jika string tidak ada di peta. Jadi, Anda mungkin ingin menguji dulu menggunakan 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;


}

Versi dengan tes untuk string yang tidak ditentukan mengikuti:

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

}
rsjaffe
sumber
4

Saya pikir alasannya adalah bahwa dalam string C bukan tipe primitif, seperti kata tomjen, pikirkan string sebagai array char, sehingga Anda tidak dapat melakukan hal-hal seperti:

switch (char[]) { // ...
switch (int[]) { // ...
grilix
sumber
3
Tanpa melihatnya, sebuah array karakter kemungkinan akan berubah menjadi char *, yang mengkonversi langsung ke tipe integral. Jadi, mungkin kompilasi, tetapi tentu saja tidak akan melakukan apa yang Anda inginkan.
David Thornley
3

Dalam c ++ string bukan warga negara kelas satu. Operasi string dilakukan melalui pustaka standar. Saya pikir, itulah alasannya. Juga, C ++ menggunakan optimasi tabel cabang untuk mengoptimalkan pernyataan kasus sakelar. Lihat tautannya.

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

chappar
sumber
2

Di C ++ Anda hanya bisa menggunakan pernyataan switch pada int dan char

CodeMonkey1313
sumber
3
Seekor char berubah menjadi int juga.
strager
Pointer juga bisa. Itu berarti Anda kadang-kadang dapat mengkompilasi sesuatu yang masuk akal dalam bahasa yang berbeda, tetapi itu tidak akan berjalan dengan benar.
David Thornley
Anda benar-benar dapat menggunakan longdan long long, yang tidak akan berubah menjadi int. Tidak ada risiko pemotongan di sana.
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;

    }
Juan Llanes
sumber
4
Sementara kode ini dapat menjawab pertanyaan, memberikan konteks tambahan tentang mengapa dan / atau bagaimana kode ini menjawab pertanyaan meningkatkan nilai jangka panjangnya.
Benjamin W.
0

dalam banyak kasus, Anda dapat melakukan kerja ekstra dengan menarik karakter pertama dari string dan mengaktifkannya. mungkin akhirnya harus melakukan switch bersarang pada karakter (1) jika kasing Anda mulai dengan nilai yang sama. siapa pun yang membaca kode Anda akan menghargai petunjuk karena sebagian besar akan prob hanya jika-lain-jika

Marshall Taylor
sumber
0

Solusi yang lebih fungsional untuk masalah sakelar:

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

Anda tidak dapat menggunakan string dalam sakelar kasus. Hanya int & char yang diizinkan. Sebagai gantinya, Anda dapat mencoba enum untuk mewakili string dan menggunakannya di blok sakelar kasus seperti

enum MyString(raj,taj,aaj);

Gunakan itu dalam pernyataan kasus swich.

anil
sumber
-1

Switch hanya berfungsi dengan tipe integral (int, char, bool, dll.). Mengapa tidak menggunakan peta untuk memasangkan string dengan angka lalu gunakan angka itu dengan sakelar?

pengucilan
sumber
-2

Itu karena C ++ mengubah switch menjadi tabel lompatan. Ia melakukan operasi sepele pada data input dan melompat ke alamat yang tepat tanpa membandingkan. Karena string bukan angka, tetapi array angka, C ++ tidak dapat membuat tabel lompatan darinya.

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

(kode dari wikipedia https://en.wikipedia.org/wiki/Branch_table )

Jean-Luc Nacif Coelho
sumber
4
C ++ tidak memerlukan implementasi sintaksis tertentu. Naif cmp/ jccimplementasi bisa sama validnya dengan Standar C ++.
Ruslan