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

ein*_*ica 13 c++ stdstring string-literals switch-statement c++17

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

switch(my_std_string) {
case "foo":  do_stuff(); break;
case "bar":  do_other_stuff(); break;
default:     just_give_up();
}
Run Code Online (Sandbox Code Playgroud)

但这是不可能的,正如古代(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();
}
Run Code Online (Sandbox Code Playgroud)

如在描述答案编译时间字符串哈希 -这是没有那么糟糕,但它实际上并没有做正是我们想要的-有碰撞的机会.

我的问题是:从那时起语言的发展(我认为主要是C++ 14)是否影响了编写一个字符串case语句的方式?或简化实现上述目的的螺母和螺栓?

具体而言,与结束C++ 17标准之中指日可待 -我很感兴趣,鉴于我们可以假设标准将包含答案.

注意:这不是关于使用switch语句的优点的讨论,也不是关于meta的线程的讨论.我问的是一个内容丰富的问题​​,所以请在此基础上回答/ up/downvote.

Yak*_*ont 5

这很容易写

switcher(expr)->*
caser(case0)->*[&]{
}->*
caser(case1)->*[&]{
};
Run Code Online (Sandbox Code Playgroud)

构建一个静态大小的case0遍历哈希表,caseN动态填充它,测试冲突==,通过查找expr,并运行相应的lambda.

即使caser(case3)->*caser(case4)->*lambda->*fallthrough可能的支持.

我没有看到迫切需要.

我认为在C++ 17中编写它也没有任何优势.


Pio*_*ycz 5

我的建议是可以用C++ 14,但if constexprstd::string_view这是一个有点esier写.

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

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

template <char ...c>
constexpr auto operator ""_cstr ()
{
    return  ConstString<c...>{};
}
Run Code Online (Sandbox Code Playgroud)

operator ==使用无模板构造tupletuple现在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来 - 定义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};
}
Run Code Online (Sandbox Code Playgroud)

还需要默认情况:

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};
}
Run Code Online (Sandbox Code Playgroud)

所以,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;
};
Run Code Online (Sandbox Code Playgroud)

可能的用法:

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);
Run Code Online (Sandbox Code Playgroud)

工作演示.


基于这篇文章,我设法以更简单的方式做ConstString .工作演示2.

增加的部分如下:

#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...>;
};
Run Code Online (Sandbox Code Playgroud)

通过更改第一个参数(20),BOOST_PP_REPEAT(20, ELEMENT_OR_NULL, #value)我们可以控制最大可能的大小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);
}
Run Code Online (Sandbox Code Playgroud)