使用Switch语句中的字符串 - 我们在C 17的位置站立

Using strings in switch statements - where do we stand with C++17?

本文关键字:位置 语句 Switch 字符串 使用 我们      更新时间:2023-10-16

我们每个人都有(可能)的童年梦想:

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

,这是不可能的,正如旧时代(2009年)的这个问题的答案中所解释的:

为什么不能在字符串上应用开关语句?

从那以后,我们看到了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();
}

正如编译时间字符串哈希的答案中所述 - 这还不错,尽管它实际上并没有完全做我们想要的 - 有可能发生碰撞。

我的问题是:从那以后(我想大多数是C 14)的语言的发展影响了人们写的字符串案例语句的方式?或简化用于实现上述的坚果和螺栓?

具体来说,在C 17标准的结论中,我对答案感兴趣,因为我们可以假设标准将包含的内容。

我的建议是可能的,但是使用if constexprstd::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..., ''};
        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 << "234n"; 
                          }),
    makeStringSwitchCase(ConstString<'a', 'b', 'c'>{}, // only C++ standard committee know why I cannot write "abc"_cstr  
                          [] { 
                              cout << "abcn"; 
                          }),
    makeStringSwitchDefaultCase([] { 
                              cout << "Defaultn"; 
                          }));
cstrSwitch.call("abc"s);

工作演示。


根据这篇文章,我设法以更轻松的方式进行固定方式。工作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 << "234n"; 
                              }),
        makeStringSwitchCase(CONST_STRING(abc){}, 
                              [] { 
                                  cout << "abcn"; 
                              }),
        makeStringSwitchDefaultCase([] { 
                                  cout << "Defaultn"; 
                              }));
    cstrSwitch.call("abc"s);
}

写作很容易

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

要构建case0caseN的静态大小的哈希表,动态填充它,测试与==的碰撞,通过expr进行查找,然后运行相应的Lambda。

甚至可以支持caser(case3)->*caser(case4)->*lambda->*fallthrough

我看不到令人信服的需求。

我认为在C 17中写下这一点。C/C :非智能者的切换 - 尤其是更新2016):

#include "cttrie.h"
...
const char *str = ...;
  TRIE(str)
    std::cout << "Not foundn";
  CASE("abc")
    std::cout << "Found abcn";
  CASE("bcd")
    std::cout << "Found bcdn";
  ENDTRIE;

在内部,在编译时间创建了一个trie并将其存储为一种类型。在运行时,它会根据str进行穿越。代码块包裹在lambdas中并在相应的叶子上执行。

这是一个简单的解决方案,用于在C/C 中模拟开关情况。

更新:包括继续版本。早期版本不能在循环中使用继续语句。正常开关案例块可以按照预期进行继续执行。但是,由于我们将用于在我们的开关案例宏中的循环,所以继续只是从开关案例块中出来,而不是从循环中出来。

这是要使用的宏:

#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

示例:用继续

的开关案例

执行

如果开关块在循环中使用,并且我们碰巧在交换机内使用继续使用,我们需要以继续(而不是end)结束开关

#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 abcn");
                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 DEFAULTn");
        CONTINUE                                        // <== Notice: Need to end the SWITCH with CONTINUE
        break; // break while(1)
    }
}

输出:

in def (continuing)
in ghi (not breaking)
in DEFAULT
  • 需要使用switch..casus..casus..continue在循环中(如果在交换机中需要继续)

  • 需要使用switch..case..ent默认情况下

  • 可以使用反向字符串比较。喜欢

    开关(" ABC")案例(str1)结束

这种比较可以打开很多比较选项,并避免使用笨拙的链条。如果没有字符的比较,就无法进行字符串比较,因此无法避免使用纤维链。至少代码与开关案例看起来很可爱。但是瓶颈是使用

  • 3个额外变量
  • 5个额外作业和
  • 每种情况的1个额外比较

因此,iTz在开发人员的酌情决定权之间选择if-else

@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..., '' };
        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 stuffn"; 
    }),
    case_(ConstString<'a', 'b', 'c'> { }, [] { 
        std::cout << "do other stuffn";
    }),
    default_( [] { 
        std::cout << "just give upn"; 
    })      
    );
}

和类似的工作演示。现在,我们真正需要的是从" abcd"式文字构建conststring。

switch语句的原始原因是,编译器可以将其映射到类似的机器操作。对于大量情况的开关,这会产生非常有效的机器代码。

对于字符串,由于需要进行比较,因此这是不可能的,因此实施的效率要差得多。与if/else/else-if子句没有什么不同。C和C 语言家族仍然具有允许在没有任何开销的情况下生成非常有效的机器代码的目标,因此,打开字符串不是一个有用的扩展名 - 如果您真的需要,则有更有效的编码方法它更有效。这也意味着将" strcmp"添加到语言语法中,以及其所有变化和变化 - 不是一个好主意。

我怀疑对于任何版本的C 。

这是另一个解决方案。但是此版本还使用了一系列比较。

  • 3个作业(包括所有情况字符串指针的数组)
  • 字符串比较直到找到匹配
  • 在整数上增加直到找到匹配

演示

#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

在C 17中,我利用std::find作为<algorithm>组的一部分。这个想法是将所有案例值字符串放在可搜索的容器中(例如std::vector)。我们将尝试找到正在搜索的字符串,然后根据找到的int索引切换。

所以,让我们开始创建一个查找器模板,例如:

template<typename T>
int find_case(std::vector<T> vHaystack, T tNeedle)
{
    int nPos(-1);
    typename std::vector<T>::iterator it =
        std::find(vHaystack.begin(), vHaystack.end(), tNeedle);
    if (it != vHaystack.cend())
        nPos = std::distance(vHaystack.begin(), it);
    return nPos;
}

find_case将返回 -1 如果找到针头内部的针头或非阴性索引,如果找到针头。

这些是用法示例:

std::vector<std::string> v1 { "Hello", "How", "are", "you" };
int n1(find_case(v1, "How"));    // Will return 1
int n2(find_case(v1, "Bye"));    // Will return -1

成为模板的一个优点是我们也可以与其他类型一起使用,例如std::wstring

现在,让我们看一下索引上的开关:

// vCases is the haystack, the vector with all case strings
// strKey is the needle, the value to be searched
switch (int nIndex; nIndex = find_case(vCases, strKey))
{
    case 0:   ...; break;
    case 1:   ...; break;
    case 2:   ...; break;
    ...
    default:
        // User gave a wrong/unexpected key
        if (nIndex < 0)
            std::cout << "Unknown case " << strKey << std::endl;
        // Our list of switch cases is missing one, at least
        else
            std::cerr << "INTERNAL: No case for " << strKey << std::endl;
}

不要忘记包括<vector><algorithm>标头。

出于此答案的范围,使用std搜索器与std::any结合使用了更强大的实现,这将使我们在常见的switch语句下将字符串和整数类型案例放在一起。

聚会很晚,这是我一段时间以前提出的解决方案,它完全遵守了请求的语法,并与C 11一起工作。

#include <uberswitch/uberswitch.hpp>
uswitch(my_std_string) {
ucase ("foo"): do_stuff(); break;
ucase ("bar"): do_other_stuff(); break;
default:       just_give_up();
}

唯一要注意的区别是用uswitch代替switchucase代替case,并围绕该值附加了括号(需要,因为那是宏。

这是代码:https://github.com/falemagn/uberswitch