Using strings in switch statements - where do we stand with C ++ 17?

Each of us (possibly) had a childhood dream to write:

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

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

Why can't the switch statement apply to strings?

Since then, we have seen the emergence of C ++ 11, which allows us to 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(); } 

as described in the answer, to compile hashing of a time string is not so bad, although it does not actually do exactly what we wanted - the probability of a collision.

My question is: since then the language has been developing (mainly C ++ 14), has influenced how it was possible to write an instruction like a string? Or simplify nuts and bolts to achieve the above?

In particular, with the conclusion of C ++ 17 , around the corner - I am interested in the answer, given that we can assume that the standard will contain.

Note. . This is not a discussion of the benefits of using switch statements, nor the flow of meta. I am asking an informative question, so please answer / up / down based on this.

+5
source share
4 answers

A small modification to @PiotrNycz's interesting answer to make the syntax a bit more like a β€œnaive” switch:

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

And a similar working demo . Now we really need to build ConstStrings from literals like "abcd".

+1
source

It would be easy to write

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

to create a hash table with a static size from case0 to caseN , enter it dynamically, check for conflicts with == , search through expr and run the corresponding lambda.

You can even support caser(case3)->*caser(case4)->*lambda and ->*fallthrough .

I do not see a compelling need.

I see no advantage in writing this in C ++ 17.

+5
source

My suggestion is possible with C ++ 14, but with if constexpr and std::string_view this is not much to write.

First - we need a constexpr line - like this:

 template <char... c> using ConstString = std::integer_sequence<char, c...>; template <char ...c> constexpr auto operator ""_cstr () { return ConstString<c...>{}; } 

operator == also easier to write without a tuple pattern and with the fact that tuple now has 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; } } 

Next - determine the switch 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..., '\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}; } 

A default requirement is also required:

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

So StringSwitch is actually an if () {} else if () {} ... else {} construct:

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

And possible use:

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

We work a demo .


I manage to make ConstString much easier based on this post. Work demo2 .

The added part is as follows:

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

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

 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); } 
+2
source

The original reason for the switch is that it can be matched by the compiler to a similar machine operation. For switches with a lot of cases, this gives a very efficient machine code.

For strings, due to the necessary comparison, this is not possible, so the implementation will be much less efficient; no different from if / else / else-if clauses. The C and C ++ language family still has a goal that allows you to create very efficient machine code without any overhead, so including lines is not something useful for extension - there are more efficient coding methods if you really need to more effective. It would also mean adding β€œstrcmp” to the syntax of the language with all its variations and whims - not a good idea.

I doubt this would be a good extension at any time for any version of C ++.

0
source

Source: https://habr.com/ru/post/1261926/


All Articles