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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在switch语句中使用字符串 - 我们在C ++ 17中的立场是什么?相关的知识,希望对你有一定的参考价值。

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

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

但这是不可能的,正如古代(2009年)对这个问题的答案所解释的那样:

Why 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();
}

正如answerCompile time string hashing所描述的那样 - 虽然它实际上并没有完全符合我们的要求 - 但它有可能发生碰撞。

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

具体来说,随着C++17 standard的结论是just around the corner - 我对我们可以假设标准将包含的答案感兴趣。

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

答案

这很容易写

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

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

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

我没有看到迫切需要。

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

另一答案

我的建议可以用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现在有了constexpr tuple的事实,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代码:

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

工作demo


我设法以更容易的方式做ConstString,基于这个post。工作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...>;
};

通过更改20中的第一个参数(BOOST_PP_REPEAT(20, ELEMENT_OR_NULL, #value)),我们可以控制ConstString的最大可能大小 - 用法如下:

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

    cstrSwitch.call("abc"s);
}
另一答案

对@ PiotrNycz有趣的回答进行了一些修改,使语法与'naive'开关更相似,允许我们写这个:

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

全面实施:

#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_

以上是关于在switch语句中使用字符串 - 我们在C ++ 17中的立场是什么?的主要内容,如果未能解决你的问题,请参考以下文章

switch语句

C++怎么用switch语句判断输入的字符

switch语句中case后面可不可以加字符串?

c语言中switch的用法 c语言中switch怎么使用

C++怎么用switch语句判断输入的字符

c语言:用switch语句处理菜单命令