C++ 在switch语句中使用字符串-使用C++;17?
我们每个人(可能)都有过童年的写作梦想:C++ 在switch语句中使用字符串-使用C++;17?,c++,switch-statement,stdstring,c++17,string-literals,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年)对这个问题的回答所解释的: 从那时起,我们看到了C++11的出现,它让我们可以做到: switch (my_hash::hash(my_std_string)) {
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();
}
正如to中所描述的——虽然它实际上并没有达到我们想要的效果,但也不是那么糟糕——存在碰撞的可能性
我的问题是:从那时起,这种语言的发展(我想主要是C++14)是否影响了人们编写类似字符串大小写语句的方式?或简化螺母和螺栓以实现上述功能
具体地说,关于存在的结论——我对给出的答案感兴趣,因为我们可以假设标准将包含什么
注意:这不是关于使用switch语句优点的讨论,也不是关于meta的线程。我在问一个信息丰富的问题,所以请在此基础上回答/向上/向下投票。写起来很容易
switcher(expr)->*
caser(case0)->*[&]{
}->*
caser(case1)->*[&]{
};
要通过caseN
构建一个静态大小的case0
哈希表,动态填充它,测试与=
的冲突,通过expr
进行查找,并运行相应的lambda
甚至可以支持caser(case3)->*caser(case4)->*lambda
和->*故障排除
我认为没有迫切的需要
我也不认为用C++17编写这篇文章有什么好处。我的建议可以用C++14编写,但是用if constexpr
和std::string_view
编写起来比较省力
首先-我们需要constexpr字符串-如下所示:
template <char... c>
using ConstString = std::integer_sequence<char, c...>;
template <char ...c>
constexpr auto operator ""_cstr ()
{
return ConstString<c...>{};
}
下一步-定义开关箱代码:
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};
}
以及可能的用途:
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);
对@PiotrNycz有趣的答案稍作修改,使语法更类似于“naive”开关,我们可以这样写:
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";
})
开关(我的标准字符串,
案例(234)cstr,[]{
std::coutswitch
语句的最初原因是编译器可以将其映射到类似的机器操作。对于具有大量事例的开关,这会生成非常高效的机器代码
对于字符串,由于需要比较,这是不可能的,因此实现效率将远远低;与IF/EFR/FIF子句没有任何区别。C和C++语言家族仍然有目标,可以在没有任何开销的情况下产生非常高效的机器代码,因此字符串的切换不是U形的。seful扩展——如果你真的需要更高效的话,有更有效的方法来编写代码。这也意味着在语言语法中添加一个“strcmp”,以及它的所有变体和变化莫测的地方——这不是一个好主意
<> P> >对于任何版本的C++,我怀疑这是一个很好的扩展。 < P>这是一个简单的解决C/C++中切换实例的解决方案。
更新:包括continue版本。早期版本不能在循环中使用continue语句。当在循环中使用常规的switch-case块时,它可以按预期执行continue。但是,由于我们在switch-case宏中使用for-loop,continue只会带出switch-case块,而不会带出循环,在循环中它是u塞德
以下是要使用的宏:
#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
示例:带continue的SWITCH-CASE
如果在循环中使用开关块,而我们恰好在开关中使用continue,则需要使用continue(而不是end)结束开关
- 需要使用SWITCH..CASE..CONTINUE在循环中(如果在开关中需要CONTINUE,也需要使用CONTINUE)
- 需要使用开关..案例..默认情况下结束
- 可以使用反向字符串比较
开关(“abc”)外壳(str1)端
这种比较可以打开大量的比较选项,避免笨拙的if-else链。如果没有逐个字符的比较,就无法进行字符串比较,因此也无法避免if-else链。至少使用SWITCH-CASE的代码看起来很可爱。但瓶颈是它使用了
- 3个额外变量
- 5额外作业和
- 每种情况下1个额外(bool)比较
因此,itz对开发人员在if-else和SWITCH-CASE之间进行选择的自由裁量权给出了另一种解决方案。但这个版本也使用了一系列比较
- 3个赋值(包括所有大小写字符串指针的数组)
- 字符串比较,直到找到匹配项为止
- 在整数上递增,直到找到匹配项
由于C++11,您可以使用(参见-特别是2016年更新):
#包括“cttrie.h”
...
常量字符*str=。。。;
特里亚(str)
晚会晚了,这里有一个我不久前提出的解决方案,它完全符合所要求的语法,也适用于c++11
#include <uberswitch/uberswitch.hpp>
uberswitch(my_std_string) {
case ("foo"): do_stuff(); break;
case ("bar"): do_other_stuff(); break;
default: just_give_up();
}
#包括
uberswitch(我的标准字符串){
案例(“foo”):dou_stuff();break;
箱子(“吧台”):不要做其他东西;打破;
默认设置:只需放弃();
}
需要注意的唯一区别是使用uberswitch
代替switch
,以及大小写
值周围的括号(需要,因为这是一个宏)
代码是这样的:我不能说我曾经做过这样的梦。您给出的哈希示例有一个错误命中的非零概率(即两个不相等的字符串有一个相等哈希的非零概率)。考虑到开关的起源
(即,利用允许基于整数值构造跳转表的机器指令)如果C++的任何版本都支持非整数的开关/ CASE,我会感到惊讶。同意,@彼得,<代码>哈希< /C>解决方案不是正式正确的…除非,也许-额外的检查是为了验证等式,所以没有明确的好处。OTHH,在语言中有一个字符串的切换,什么是BeNFF?
#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...>;
};
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);
}
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..., '\0' };
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 stuff\n";
}),
case_(ConstString<'a', 'b', 'c'> { }, [] {
std::cout << "do other stuff\n";
}),
default_( [] {
std::cout << "just give up\n";
})
);
}
#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
#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 abc\n");
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 DEFAULT\n");
CONTINUE // <== Notice: Need to end the SWITCH with CONTINUE
break; // break while(1)
}
}
in def (continuing)
in ghi (not breaking)
in DEFAULT
#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
#include "cttrie.h"
...
const char *str = ...;
TRIE(str)
std::cout << "Not found\n";
CASE("abc")
std::cout << "Found abc\n";
CASE("bcd")
std::cout << "Found bcd\n";
ENDTRIE;
#include <uberswitch/uberswitch.hpp>
uberswitch(my_std_string) {
case ("foo"): do_stuff(); break;
case ("bar"): do_other_stuff(); break;
default: just_give_up();
}