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

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

Every one of us has (probably) had the childhood dream of writing:

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

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

but this is not possible, as is explained in the answers to this question from the olden days (2009):

为什么switch语句不能应用于字符串?

从那时起,我们见证了 C++11 的出现,它让我们走得更远:

Since then we've seen the advent of C++11, which lets us go as far as:

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 中所述,编译时间字符串散列 - 这还不错,虽然它实际上并没有完全按照我们的意愿做 - 有可能发生冲突.

as described in an answer to Compile time string hashing - which is not so bad, although it doesn't actually do exactly what we wanted - there's a chance of collision.

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

My question is: Has the development of the language since then (mostly C++14 I suppose) affected the way one would write a sort-of-a string case statement? Or simplified the nuts-and-bolts for achieving the above?

具体来说,以 结尾C++17 标准 指日可待 - 我在鉴于我们可以假设标准将包含的内容,对答案感兴趣.

Specifically, with the conclusion of the C++17 standard being just around the corner - I'm interested in the answer given what we can assume the standard will contain.

推荐答案

我的建议可以使用 C++14,但使用 if constexprstd::string_view写起来有点麻烦.

My proposal is possible with C++14, but with if constexpr and std::string_view it is a little esier to write.

首先 - 我们需要 constexpr 字符串 - 像这样:

First - we need constexpr string - like this one:

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 == is also easier to write with template-less construction of tuple and with the fact that tuple has now 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;
    }
}

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

Next thing - define switch-case code:

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 {} 构造:

So, the StringSwitch - actually, it is if () {} else if () {} ... else {} construction:

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
"; 
                          }),
    makeStringSwitchCase(ConstString<'a', 'b', 'c'>{}, // only C++ standard committee know why I cannot write "abc"_cstr  
                          [] { 
                              cout << "abc
"; 
                          }),
    makeStringSwitchDefaultCase([] { 
                              cout << "Default
"; 
                          }));

cstrSwitch.call("abc"s);

工作演示.

基于此 ,我设法以更简单的方式执行 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 的最大可能大小- 用法如下:

By changing first parameter (20) in BOOST_PP_REPEAT(20, ELEMENT_OR_NULL, #value) we can control the maximum possible size of ConstString - and the usage is as follows:

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

    cstrSwitch.call("abc"s);
}

相关文章