web-dev-qa-db-ja.com

switchステートメントでの文字列の使用-C ++ 17のどこに立つのですか?

私たち全員が(おそらく)子供の頃に書くという夢を見ました。

switch(my_std_string) {
case "foo":  do_stuff(); break;
case "bar":  do_other_stuff(); break;
default:     just_give_up();
}

しかし、これは不可能です。昔(2009年)のこの質問への回答で説明されています。

なぜswitchステートメントを文字列に適用できないのですか?

それ以来、C++ 11の登場を目にしてきました。

switch (my_hash::hash(my_std_string)) {
case "foo"_hash:  do_stuff(); break;
case "bar"_hash:  do_other_stuff(); break;
default:          just_give_up();
}

answer から コンパイル時の文字列ハッシュ で説明されているように、これはそれほど悪いことではありませんが、実際には期待どおりに動作しませんが、衝突する可能性があります。

私の質問は次のとおりです。それ以降の言語の開発(ほとんどの場合C++ 14)は、一種の文字列のcaseステートメントの記述方法に影響を与えましたか?または、上記を達成するための基本事項を簡素化しましたか?

具体的には、 C++ 17標準角を曲がったところ であるという結論で、標準に含まれると想定できるものを考えれば、私は答えに興味があります。

注:これは、switchステートメントを使用するメリットの説明でも、メタのスレッドでもありません。私は有益な質問をしているので、それに基づいて答え/アップ/ダウン投票してください。

14
einpoklum

私の提案はC++ 14で可能ですが、_if constexpr_および_std::string_view_を使用すると、書くのが少し面倒になります。

まず、constexpr文字列が必要です-このように:

_template <char... c>
using ConstString = std::integer_sequence<char, c...>;

template <char ...c>
constexpr auto operator ""_cstr ()
{
    return  ConstString<c...>{};
}
_

_operator ==_は、テンプレートなしのTupleの構築と、Tupleがconstexpr _operator ==_を持つようになったことで、より簡単に記述できます。

_template <char... c1, char ...c2>
constexpr bool operator == (ConstString<c1...>, ConstString<c2...>)
{
    if constexpr (sizeof...(c1) == sizeof...(c2)) // c++17 only
    {
        return Tuple{c1...} == Tuple{c2...};  // c++17 only
    }
    else
    {
        return false;
    }
}
_

次のこと-スイッチケースコードを定義します:

_template <typename Callable, typename Key>
class StringSwitchCase;

template <typename Callable, char ...c>
struct StringSwitchCase<Callable, ConstString<c...>>
{
    constexpr bool operator == (const std::string_view& str) // c++17 only
    {
        constexpr char val[] = {c..., '\0'};
        return val == str;
    }
    Callable call;
    static constexpr ConstString<c...> key{};
};

template <typename Callable, char ...c>
constexpr auto makeStringSwitchCase(CString<c...>, Callable call)
{
    return StringSwitchCase<Callable, ConstString<c...>>{call};
}
_

デフォルトのケースも必要になります:

_template <typename Callable>
struct StringSwitchDefaultCase
{
    constexpr bool operator == (const std::string_view&)
    {
        return true;
    }
    Callable call;
};

template <typename Callable>
constexpr auto makeStringSwitchDefaultCase(Callable call)
{
    return StringSwitchDefaultCase<Callable>{call};
}
_

したがって、StringSwitch-実際にはif () {} else if () {} ... else {}構成です:

_template <typename ...Cases>
class StringSwitch
{
public:
    StringSwitch(Cases&&... cases) : cases(std::forward<Cases>(cases)...) {}

    constexpr auto call(const std::string_view& str)
    {
        return call<0u>(str);
    }
private:
    template <std::size_t idx>
    constexpr auto call(const std::string_view& str)
    {
        if constexpr (idx < sizeof...(Cases))
        {
            if (std::get<idx>(cases) == str)
            {
                return std::get<idx>(cases).call();
            }
            return call<idx + 1>(str);
        }
        else
        {
            return;
        }
    }

    std::Tuple<Cases...> cases;
};
_

そして可能な使用法:

_StringSwitch cstrSwitch(   
    makeStringSwitchCase(234_cstr, 
                          [] { 
                              cout << "234\n"; 
                          }),
    makeStringSwitchCase(ConstString<'a', 'b', 'c'>{}, // only C++ standard committee know why I cannot write "abc"_cstr  
                          [] { 
                              cout << "abc\n"; 
                          }),
    makeStringSwitchDefaultCase([] { 
                              cout << "Default\n"; 
                          }));

cstrSwitch.call("abc"s);
_

動作中 デモ


この post に基づいて、ConstStringをはるかに簡単な方法で管理しています。動作 demo2

追加された部分は次のとおりです。

_#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/comma_if.hpp>

#define ELEMENT_OR_NULL(z, n, text) BOOST_PP_COMMA_IF(n) (n < sizeof(text)) ? text[n] : 0
#define CONST_STRING(value) typename ExpandConstString<ConstString<BOOST_PP_REPEAT(20, ELEMENT_OR_NULL, #value)>, \
                                                       ConstString<>, sizeof(#value) - 1>::type

template <typename S, typename R, int N>
struct ExpandConstString;
template <char S1, char ...S, char ...R, int N>
struct ExpandConstString<ConstString<S1, S...>, ConstString<R...>, N> :
       ExpandConstString<ConstString<S...>, ConstString<R..., S1>, N - 1>
{};
template <char S1, char ...S, char ...R>
struct ExpandConstString<ConstString<S1, S...>, ConstString<R...>, 0>
{
    using type = ConstString<R...>;
};
_

BOOST_PP_REPEAT(20, ELEMENT_OR_NULL, #value)の最初のパラメーター(_20_)を変更することで、ConstStringの最大サイズを制御できます。使用方法は次のとおりです。

_int main() {
    StringSwitch cstrSwitch(
        makeStringSwitchCase(CONST_STRING(234){}, 
                              [] { 
                                  cout << "234\n"; 
                              }),
        makeStringSwitchCase(CONST_STRING(abc){}, 
                              [] { 
                                  cout << "abc\n"; 
                              }),
        makeStringSwitchDefaultCase([] { 
                                  cout << "Default\n"; 
                              }));

    cstrSwitch.call("abc"s);
}
_
5
PiotrNycz

書きやすいと思います

_switcher(expr)->*
caser(case0)->*[&]{
}->*
caser(case1)->*[&]{
};
_

_case0_からcaseNまでの静的サイズのハッシュテーブルを作成し、動的に入力し、_==_との衝突をテストし、exprを介してルックアップを実行し、対応するラムダ。

caser(case3)->*caser(case4)->*lambdaおよび_->*fallthrough_もサポートされます。

やむを得ないニーズはないと思います。

これをC++ 17で書くことにも利点はないようです。

これは、C/C++でスイッチケースをシミュレートするための簡単なソリューションです。

UPDATE:continueバージョンを含みます。以前のバージョンでは、ループ内でcontinueステートメントを使用できません。通常のswitch-caseブロックは、ループで使用すると、期待どおりcontinueを実行できます。しかし、SWITCH-CASEマクロでforループを使用しているので、continueはSWITCH-CASEブロックを取り出しますが、取り出しませんそれが使用されているループの。

使用するマクロは次のとおりです。

#ifndef SWITCH_CASE_INIT
#define SWITCH_CASE_INIT
    char __switch_continue__;

    #define SWITCH(X)   __switch_continue__=0; \
                    for (char* __switch_p__=X, __switch_next__=1; __switch_p__!=0 ; __switch_next__=2) { \
                        if (__switch_next__==2) { __switch_continue__=1; break;
    #define CASE(X)         } if (!__switch_next__ || !(__switch_next__ = strcmp(__switch_p__, X))) {
    #define DEFAULT         } {
    #define END         __switch_p__=0; }}
    #define CONTINUE    __switch_p__=0; }} if (__switch_continue__) { continue; }
#endif

例:SWITCH-CASE withcontinue

[〜#〜]実行[〜#〜]

SWITCHブロックがループで使用されていて、たまたまSWITCH内でcontinueを使用している場合は、SWITCHを(ENDではなく)CONTINUEで終了する必要があります。

#include <stdio.h>
#include <string.h>

#ifndef SWITCH_CASE_INIT
#define SWITCH_CASE_INIT
    char __switch_continue__;

    #define SWITCH(X)   __switch_continue__=0; \
                        for (char* __switch_p__=X, __switch_next__=1; __switch_p__!=0 ; __switch_next__=2) { \
                            if (__switch_next__==2) { __switch_continue__=1; break;
    #define CASE(X)         } if (!__switch_next__ || !(__switch_next__ = strcmp(__switch_p__, X))) {
    #define DEFAULT         } {
    #define END         __switch_p__=0; }}
    #define CONTINUE    __switch_p__=0; }} if (__switch_continue__) { continue; }
#endif


int main()
{
    char* str = "def";
    char* str1 = "xyz";

    while (1) {
        SWITCH (str)
            CASE ("abc")
                printf ("in abc\n");
                break;

            CASE ("def")                                
                printf("in def (continuing)\n");
                str = "ghi";
                continue;                               // <== Notice: Usage of continue (back to enclosing while loop)

            CASE ("ghi")                                // <== Notice: break; not given for this case. Rolls over to subsequent CASEs through DEFAULT
                printf ("in ghi (not breaking)\n");

            DEFAULT
                printf("in DEFAULT\n");

        CONTINUE                                        // <== Notice: Need to end the SWITCH with CONTINUE

        break; // break while(1)
    }
}

出力:

in def (continuing)
in ghi (not breaking)
in DEFAULT
  • ループ内でSWITCH..CASE..CONTINUEを使用する必要があります(スイッチ内で続行が必要な場合も同様)

  • デフォルトでSWITCH..CASE..ENDを使用する必要があります

  • 逆文字列比較を使用できます。お気に入り

    SWITCH( "abc")CASE(str1)END

この種の比較により、多くの比較オプションが開かれ、扱いにくいif-elseチェーンが回避されます。文字列の比較は、文字ごとの比較なしでは行うことができないため、if-elseチェーンを回避できません。少なくともコードはSWITCH-CASEでかわいく見えます。しかし、ボトルネックはそれが使用することです

  • 3つの追加変数
  • 5つの追加割り当てと
  • CASEごとに1つの追加(ブール)比較

If-elseからSWITCH-CASEへの切り替えに関する開発者の裁量について

3
Ramu

C++ 11以降は smilingthax/cttrie (cf. C/C++:switch for non-integers -esp。Update 2016)を使用できます:

#include "cttrie.h"
...
const char *str = ...;

  TRIE(str)
    std::cout << "Not found\n";
  CASE("abc")
    std::cout << "Found abc\n";
  CASE("bcd")
    std::cout << "Found bcd\n";
  ENDTRIE;

内部的には、コンパイル時に Trie が作成され、型として保存されます。実行時には、strに従ってトラバースされます。コードブロックはラムダでラップされ、対応するリーフで実行されます。

3
smilingthax

@PiotrNyczの興味深い答えを少し変更して、構文を「単純」スイッチに少し似たものにすると、次のように記述できます。

switch_(my_std_string, 
case_(234_cstr, [] {     
    std::cout << "do stuff with the string \"234\" \n"; 
}),
case_(ConstString<'a', 'b', 'c'> { }, [] { 
    std::cout << "do other stuff with the string \"abc\"\n";
}),
default_( [] { 
    std::cout << "just give up.\n"; 
})      

完全な実装:

#include <iostream>
#include <array>
#include <Tuple>
#include <string>
#include <type_traits>
#include <utility>


template<char ... c>
using ConstString = std::integer_sequence<char, c...>;

template <char ...c>
constexpr auto operator ""_cstr ()
{
    return ConstString<c...> {};
}

template<char ... c1, char ...c2>
constexpr bool operator == (ConstString<c1...>, ConstString<c2...>) 
{
    if constexpr (sizeof...(c1) == sizeof...(c2)) {
        return std::Tuple {c1...} == std::Tuple {c2...};
    }
    else { return false; }
}

template<typename Callable, typename Key>
class SwitchCase;

template<typename Callable, char ...c>
struct SwitchCase<Callable, ConstString<c...>> {
    constexpr bool operator == (const std::string_view& str) {
        constexpr char val[] = { c..., '\0' };
        return val == str;
    }
    const ConstString<c...> key;
    Callable call;
};

template<typename Callable, char ...c>
constexpr auto case_(ConstString<c...> key, Callable call) 
{
    return SwitchCase<Callable, ConstString<c...>> { key, call };
}

template<typename Callable>
struct SwitchDefaultCase {
    constexpr bool operator == (const std::string_view&) { return true; }
    Callable call;
};

template<typename Callable>
constexpr auto default_(Callable call) 
{
    return SwitchDefaultCase<Callable> { call };
}

template<typename ...Cases>
class switch_ {
public:
    // I thought of leaving this enabled, but it clashes with the second ctor somehow
    // switch_(Cases&&... cases) : cases(std::forward<Cases>(cases)...) {}

    constexpr auto call(const std::string_view& str) {
        return call<0u>(str);
    }

    switch_(const std::string_view&& str, Cases&&... cases) :
            cases(std::forward<Cases>(cases)...) {
        call<0u>(str);
    }

private:
    template<std::size_t idx>
    constexpr auto call(const std::string_view& str) {
        if constexpr (idx < sizeof...(Cases)) {
            if (std::get<idx>(cases) == str) {
                return std::get<idx>(cases).call();
            }
            return call<idx + 1>(str);
        }
        else { return; }
    }

    std::Tuple<Cases...> cases;
};

int main() {
    std::string my_std_string("abc");
    std::cout << "What is \"" << my_std_string << "\"?\n";

    switch_(my_std_string, 
    case_(234_cstr, [] {     
        std::cout << "do stuff\n"; 
    }),
    case_(ConstString<'a', 'b', 'c'> { }, [] { 
        std::cout << "do other stuff\n";
    }),
    default_( [] { 
        std::cout << "just give up\n"; 
    })      
    );
}

そして、同様の 実用的なデモ 。ここで本当に必要なのは、「abcd」タイプのリテラルからConstStringを構築することです。

2
einpoklum

これが別の解決策です。ただし、このバージョンでは一連の比較も使用します。

  • 3つの割り当て(すべてのCASE文字列ポインターの配列を含む)
  • 一致が見つかるまでの文字列比較
  • 一致が見つかるまで整数をインクリメントします

[〜#〜] demo [〜#〜]

#include <stdio.h>
#include <string.h>

#define SWITCH(X, ...) \
            char * __switch_case_ ## X ## _decl[] = {__VA_ARGS__}; \
            int __switch_case_ ## X ## _i=0, __switch_case_ ## X ## _size = sizeof(__switch_case_ ## X ## _decl)/sizeof(char*); \
            while (__switch_case_ ## X ## _i < __switch_case_ ## X ## _size && strcmp(X, __switch_case_ ## X ## _decl[__switch_case_ ## X ## _i])){ __switch_case_ ## X ## _i++; } \
            switch (__switch_case_ ## X ## _i)


int main()
{
    char * str = "def";

    SWITCH (str, "abc", "def", "ghi", "jkl")
    {
    case 0:
        printf (str);
        break;
    case 1:
        printf (str);
        break;
    case 2:
        printf (str);
        break;
    case 3:
        printf (str);
        break;
    default:
        printf ("default");
    }

    return 0;
}

出力:

def
1
Ramu