4

I want to switch over possible values of two integers, or in another case two bools. For the sake of discussion, suppose I've done

auto mypair = std::make_pair(foo, bar);

How can I achieve the equivalent of

switch(mypair) {
case make_pair(true, false): cout << "true and false"; break;
case make_pair(false, true)  cout << "false and true"; break;
case default: cout << "something else";
}

with C++11? (C++14/17 also relevant if that helps)?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • I guess [this](http://en.cppreference.com/w/cpp/language/switch) would help. See in particular what's expected as a *condition* and how it is defined the *constant_expression* of the *case* statement before to proceed. What do you expect exactly from your snippet? – skypjack Feb 12 '16 at 09:32

2 Answers2

7

You can only switch on an integral type, but if you can devise a function to map your pair (or any complex type) to an integral type, you can declare it as constexpr (C++11) to indicate it can be resolved at compile time. Then it is acceptable as a case expression.

Simple example:

enum Action { peel, eat, juice };
enum Fruit { apple, orange, banana };

constexpr unsigned int switch_pair(Action a, Fruit f) {
   return (a << 16) + f;
}

Then define the switch like this:

switch(switch_pair(mypair.first,mypair.second))
{
   case switch_pair(peel,apple):  std::cout << "Peeling an apple" << std::endl; break;
   case switch_pair(eat,apple):   std::cout << "Eating an apple" << std::endl; break;
   case switch_pair(juice,apple): std::cout << "Juicing an apple" << std::endl; break;
   default:
      throw std::runtime_error("We only have apples!");
}
Michael D.
  • 195
  • 1
  • 9
  • 1
    Wow - this is the first time I have see a constexpr use case that is a coding win for my codebase. Love it! Thank you! – GeePokey May 29 '21 at 03:54
4

C++'s switch statement doesn't have the pattern matching power of many other languages. You'll need to take a slightly different approach.

Here's a possibility I threw together:

pair_switch(my_pair,
    std::make_tuple(true, false, []{ std::cout << "true and false"; }),
    std::make_tuple(false, true, []{ std::cout << "false and true"; }));

You supply a std::pair<bool,bool> and a set of cases as std::tuples, where the first two elements match the pair you pass in and the third element is a function to call for that case.

The implementation has a few template tricks, but should be pretty usable:

template <typename... Ts>
void pair_switch(std::pair<bool,bool> pair, Ts&&... ts) {
    //A table for the cases
    std::array<std::function<void()>, 4> table {};

    //Fill in the cases
    (void)std::initializer_list<int> {
        (table[std::get<0>(ts)*2 + std::get<1>(ts)] = std::get<2>(ts), 0)...
    };

    //Get the function to call out of the table
    auto& func = table[pair.first*2 + pair.second];

    //If there is a function there, call it
    if (func) {
        func();   
    //Otherwise, throw an exception
    } else {
        throw std::runtime_error("No such case");   
    }
}

Live Demo

TartanLlama
  • 63,752
  • 13
  • 157
  • 193
  • OK, you deserve a +1 in any case, at least for the way you used the `initializer_list` to fill in the array the cases. Chapeau. Does that statement simply unroll the parameter pack into the array (thus discarding the list makes sense) or there is another purpose I can't see? Love the `, 0` part, really, never thought something like that to unroll the pack, really clever!! – skypjack Feb 12 '16 at 10:43
  • It uses the fact that you can unpack parameter packs in the context of `initializer_list`s, then just fills the array as a side effect and discards the `initializer_list`. This uglyness will be fixed when fold-expressions are in the language. – TartanLlama Feb 12 '16 at 10:44
  • OK, it works as I got it, nice. Really interesting and good an idea, indeed. Fold-expressions means 202y, am I wrong? – skypjack Feb 12 '16 at 10:49
  • @skypjack Hopefully 1z/17 – TartanLlama Feb 12 '16 at 10:55