4

I came across this question: Why switch statement cannot be applied on strings? and wonder if the answer:

The reason why has to do with the type system. C/C++ doesn't really support strings as a type. It does support the idea of a constant char array but it doesn't really fully understand the notion of a string.

still holds true, even with std:string within C++11/14. Is there an alternative to having severals else if(...)'s?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
dani
  • 3,677
  • 4
  • 26
  • 60
  • thats pretty standard fare. – Daniel A. White Feb 14 '16 at 00:19
  • 3
    std::string was part of C++98/03 – Nicol Bolas Feb 14 '16 at 00:29
  • 1
    A solution: http://stackoverflow.com/a/16388610/3093378 You can use [user defined literals](http://en.cppreference.com/w/cpp/language/user_literal) to write the hash function like `"some_string"_hash`, so the end up code will look quite nice. – vsoftco Feb 14 '16 at 00:33
  • It is exceedingly unlikely that, even if language support were to be added for `switch`ing on strings, compilers would do anything different than they already do for a series of `if` statements. And I don't think a `switch` block is any more readable than a series of `if` statements. So I don't really see the advantage in this. – Cody Gray - on strike Feb 14 '16 at 14:29
  • 1
    @vsoftco - it is not fullproof though, although rarely in practice, it is theoretically possible to get the same hash for different strings. – dtech Feb 14 '16 at 14:52

4 Answers4

3

Yes, it still holds.
As stated here, the condition can be:

any expression of integral or enumeration type, or of a class type contextually implicitly convertible to an integral or enumeration type, or a declaration of a single non-array variable of such type with a brace-or-equals initializer.

I came across that question a couple of days ago, and I guess you can figure out an alternative solution for the if/else chain from there.
It mostly depends on your actual problem if it's possible, anyway the basic idea is to use a map of callable objects from which to access using your objects (strings in this case) as a key. That map has to be filled somehow before to use it, of course.

Community
  • 1
  • 1
skypjack
  • 49,335
  • 19
  • 95
  • 187
3

Is there an alternative to having severals else if(...)'s?

Instead of a switch statement, you could write your own switch function.

Maybe it would look something like this:

#include <iostream>
#include <string>
#include <utility>
#include <tuple>

//pass in a bool as to whether or not we break
//make macro so it looks more like a switch statement
#define BREAK true

//template details used by outside functions
namespace switch_impl{

  //represents a case statement
  template <typename T, typename U>
  struct Case_Statement{
    U value;
    T expression;
    bool breaks;

    Case_Statement(U value, T expression, bool breaks=false)
    : value(value)
    , expression(expression)
    , breaks(breaks)
    {}

  };

  //recursive template unpacking to evaluate in a fashion similar to switch statements
  template<std::size_t I = 0, typename C, typename... Tp>
  inline typename std::enable_if<I == sizeof...(Tp), void>::type
    evaluate(C comparator, bool found, std::tuple<Tp...>& t)
    { }

  template<std::size_t I = 0, typename C, typename... Tp>
  inline typename std::enable_if<I < sizeof...(Tp), void>::type
    evaluate(C comparator, bool found, std::tuple<Tp...>& t)
    {
      if (std::get<I>(t).value == comparator || found){
        std::get<I>(t).expression();
        if (!std::get<I>(t).breaks){
          evaluate<I + 1, C, Tp...>(comparator,true,t);
        }
      }else{
          evaluate<I + 1, C, Tp...>(comparator,false,t);
      }
    }
}

//base functions to compose so that it looks like a switch statement
template<typename T, typename... Statements>  
void Switch(T comparator, Statements... statements)  
{
  auto t = std::make_tuple(statements...);
  switch_impl::evaluate(comparator,false,t);
}

template<typename T, typename U>
auto Case(U value, T expression, bool breaks=false) -> switch_impl::Case_Statement<T,U>{
  return switch_impl::Case_Statement<T,U>(value,expression,breaks);
}


//example usage
int main(){

  //c style switch example:
  switch (2){

    case 1:
    std::cout << "1\n";
    break;

    case 2:
    std::cout << "2\n";

    case 3: 
    std::cout << "3\n";
    break;

    case 4: 
    std::cout << "4\n";
    break;
  }

  //c++ functional switch example:
  Switch("2",

    Case("1",[&](){
      std::cout << "1\n";
    },BREAK),

    Case("2",[&](){
      std::cout << "2\n";
    }),

    Case("3",[&](){
      std::cout << "3\n";
    },BREAK),

    Case("4",[&](){
      std::cout << "4\n";
    },BREAK)
  );

}

I've left out the default case, but you get the idea.
In fact, you might realize that this is slightly more powerful than constant expressions.

Welcome to the world of pattern matching,
which is a language feature, I think C++ could definitely use.

Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
  • A lot uglier and more verbose than series of else ifs :) – dtech Feb 14 '16 at 14:30
  • @ddriver "Ugly" is subjective. I don't see how it is more verbose for the person who uses the Switch: `if(value == "1") { ... }` is not much shorter than `Case("1", [&]() { ... }`. The entire template magic is written only once and hidden in a header file/module unit. The difference is that this solution shown here shows the intent better: that you want to do something different depending on one value, and not depending on an arbitrary sequence of else-ifs that might be the same as a switch case after thorough reading – KABoissonneault Feb 14 '16 at 14:55
  • I don't quite get what your code does. A solution for sure, if the "case-switch" is needed alot. – dani Feb 14 '16 at 16:33
0

Alternative is to have a map of string to std::function, but it is quite ugly IMO esp if you need to have default case.

#include <iostream>
#include <functional>
#include <string>
#include <map>

using namespace std;

static const map<string, function<void (const string&)>> handlers {
    { "AAPL", [](const auto& str) {cout << str << " Calif\n";}},
    { "MSFT", [](const auto& str) {cout << str << " Wash\n";}}
    };
static const function <void (const string&)> default_handler = [] (const auto& str) 
    {cout << str << " Narnia\n";};


void f(const string& str) {
    const auto it = handlers.find(str);
    if (it !=handlers.end()) {
        it->second(str);
    }
    else 
    {
        default_handler(str);
    }
}

int main() {
    f("ABC");
    f("AAPL");
    f("MSFT");;
}
NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277
0

The way c# does it internally is it gets the hashcode of a string and does a switch on that.

trinalbadger587
  • 1,905
  • 1
  • 18
  • 36