c++ - 在 switch 语句中使用字符串——我们在 C++17 中的立场是什么?

标签 c++ switch-statement stdstring c++17 string-literals

我们每个人(可能)都有儿时写作的梦想:

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

但这是不可能的,正如过去(2009 年)对这个问题的回答中所解释的那样:

Why the switch statement cannot be applied on strings?

从那时起,我们见证了 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 中所述至Compile time string hashing - 这还不错,尽管它实际上并没有完全按照我们的意愿行事 - 有可能发生碰撞。

我的问题是:从那时起该语言的发展(我想主要是 C++14)是否影响了人们编写某种字符串 case 语句的方式?还是简化了实现上述目标的具体细节?

具体来说,随着 C++17 standard 的结束正在 just around the corner - 鉴于我们可以假设标准将包含的内容,我对答案很感兴趣。

最佳答案

我的建议可以使用 C++14,但是使用 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 运营商==:

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

接下来 - 定义 switch-case 代码:

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

工作 demo .


基于 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);
}

关于c++ - 在 switch 语句中使用字符串——我们在 C++17 中的立场是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41337256/

相关文章:

c++ - std::string 分配的内存是否会影响性能?

c++ - std::string::operator[] 的结果地址是否指向一个可写的、以 nul 结尾的缓冲区?

c++ - 在 Linux 上使用 g++ 输出错误,在 Windows 上更正

c++ - 没有大量 getter/mutator 的优雅的面向对象的成员访问

c++ - 持有共享资源的类的合理设计

C++ 运行时检查失败 #2 - 变量 'theArray' 周围的堆栈已损坏

ios - 在Swift中的通用类中切换通用类型

java - 不使用switch实现Setter和Getter

基于下拉列表选项加载内容的 PHP 表单

c++ - 使用 `std::string::c_str()` 将 `mkdtemp()` 的结果传递给 `const_cast<char*>()`