1

Switch statements can be lengthy, so I wrote the following class (with example usage in main()):

#include <iostream>
using namespace std;

template <typename In, typename Out, In In1, Out Out1, In In2, Out Out2, Out DefaultOut>
struct SwitchMap
{
    Out operator[](const In& input) const
    {
        switch (input) {
        case In1: return Out1;
        case In2: return Out2;
        default: return DefaultOut;
        }
    }
};

int main(int, char **)
{
    SwitchMap<unsigned int, unsigned int, 3, 1, 4, 2, 3> myMap;

    cout << myMap[3] << endl; // prints 1
    cout << myMap[4] << endl; // prints 2
    cout << myMap[5] << endl; // prints 3

    return 0;
}

There are two goals I'd like to achieve: 1. I'd like to make SwitchMap accept an arbitrary number of cases (where the number of Outs is one greater than the number of Ins to provide a default return value). My example only works for two Ins, two Outs, and one default Out. 2. Is it possible to make the declaration for map look like this: SwitchMap<3, 1, 4, 2, 3> myMap;? It would be great if the types could be automatically deduced (although I realize this might result in the compiler choosing int instead of unsigned int, which I'm willing to deal with).

Can I achieve either or both of these goals?

Zark Bardoo
  • 468
  • 5
  • 15
  • 3
    Why not just use a `std::map` ? – Paul R Dec 18 '14 at 20:56
  • 2
    You might also use a jump table, depending on who, what, when, where, why... – John Dibling Dec 18 '14 at 20:59
  • @PaulR: I guess there's no reason in particular. For one, I'm curious. For two, why ever write a switch statement instead of a `std::map`? My understanding is that a `std::map` has to be constructed at run-time whereas a switch statement like the one above is a tiny object (or need not exist at all if we make the operator[] static) and the code exists at compile-time. – Zark Bardoo Dec 18 '14 at 21:04
  • 3
    A switch statement is more appropriate when there is *flow of control* - if you just want to map input values to output values then a switch statement is not very efficient or concise unless you only have a few values - a map makes more sense. – Paul R Dec 18 '14 at 21:08
  • @ZarkBardoo - Hopefully you don't really name your variables the same as standard classes. – PaulMcKenzie Dec 18 '14 at 21:22
  • @PaulMcKenzie Oops! That's embarrassing. I'll rename it to `myMap`. – Zark Bardoo Dec 18 '14 at 21:23
  • 3
    @ZarkBardoo - Also, the function signature for `main()` is wrong. – PaulMcKenzie Dec 18 '14 at 21:25
  • How do you expect to prevent duplicate keys? Like `SwitchMap myMap;` Or do you not care? – R Sahu Dec 18 '14 at 21:29
  • @ZarkBardoo - What if the number of arbitrary cases is 100? Are you going to code a switch statement with 100 `case`s? Also, if you are going down this route, `operator[]` needs to be overloaded twice, the second time for const `SwitchMap`'s. – PaulMcKenzie Dec 18 '14 at 21:34
  • Relevant http://stackoverflow.com/questions/23092121/replaceing-switch-statements-when-interfaceing-between-templated-and-non-templat – IdeaHat Dec 18 '14 at 21:58
  • I don't see how you could do it with a switch, but it would be possible to use a bit of TMP to generate a sequence of if-statements. Is everything constexpr? – kec Dec 18 '14 at 22:53
  • @RSahu - presumably such an implementation wouldn't compile, just as its switch() counterpart wouldn't compile – Zark Bardoo Dec 18 '14 at 23:04
  • @PaulMcKenzie - It's not a one-size-fits all, but neither is a switch statement. Why do you ask about the number of cases? I think that was an oversight on my part... There's no real reason operator[] wasn't marked const in my code. I'll do that now. @kec - Everything is constexpr. Perhaps I'm wrong, but they would need to be to be included as a template parameter, right? And surely each `case` needs to be `constexpr`. – Zark Bardoo Dec 18 '14 at 23:09
  • @PaulMcKenzie Surprised I didn't catch the main() signature being wrong. It compiled in Visual Studio 2013 =P – Zark Bardoo Dec 18 '14 at 23:11
  • Then you can do it with TMP to generate a sequence of if-statements. If that meets your requirements, then I might give it a shot. Note that it would be separate function calls, but would rely on inlining so that it would be effectively a single sequence of if-statements. If C++14 is okay, we can make it all constexpr, so that the result is also constexpr. – kec Dec 18 '14 at 23:36

2 Answers2

0

Here is an example using if-statements. Note that a good compiler will produce code that is just as efficient as a switch. The result is also constexpr.

#include <iostream>

template <typename T, T DEF>
inline constexpr T SwitchMap_helper(T) {
    return DEF;
}

template <typename T, T IN, T OUT, T... REST>
inline constexpr T SwitchMap_helper(T v) {
    return v == IN ? OUT : SwitchMap_helper<T, REST...>(v);
}

template <typename T, T... CASES>
struct SwitchMap {
    constexpr T operator[](T v) {
        return SwitchMap_helper<T, CASES...>(v);
    }
};

int
main() {

    SwitchMap<int, 1, 2, 3, 4, 5> sm1;
    std::cout << sm1[1] << std::endl;
    std::cout << sm1[3] << std::endl;
    std::cout << sm1[10] << std::endl;
}
kec
  • 2,099
  • 11
  • 17
0

Here is a little workaround which uses an indirection over a function template:

#include<tuple>
#include<iostream>

template<typename ... Args>
struct SwitchMap
{
    static const size_t ArgsSize = sizeof ... (Args); 
    using ArgsTuple = std::tuple<Args ...>;
    using InType = typename std::tuple_element<0,ArgsTuple>::type;
    using OutType = typename std::tuple_element<1,ArgsTuple>::type;

    std::tuple<Args ...> t;
    SwitchMap(Args const& ... args) : t(std::make_tuple(args ...))
    {
        static_assert((ArgsSize & 1) &&  (ArgsSize > 2), " ");
        //here possibly add another check for the consistency of the input types
    }

    template<int> struct int2type{};

    template<int N>
    OutType get(InType const& in, int2type<N>) const
    {
        return in == std::get<N>(t) ? std::get<N+1>(t) : get(in,int2type<N+2>());
    }

    OutType get(InType const& in, int2type<ArgsSize-1>) const
    {
        return std::get<ArgsSize-1>(t);
    }

    OutType operator[](InType const& in) const
    {
        return get(in,int2type<0>());
    }

};


template<typename ... Args>
auto makeSwitchMap(Args const& ... args)
{
    return SwitchMap<Args ...>(args ...);    
}

Call it via

int main()
{
    auto switchMap = makeSwitchMap(1,std::string("Say Hi"),2,std::string("Say Goodbye"),std::string("Say nothing"));
    std::cout<<switchMap[1]<<std::endl;
    std::cout<<switchMap[2]<<std::endl;
    std::cout<<switchMap[4]<<std::endl;
}

which produces

Say Hi
Say Goodbye
Say nothing

DEMO

davidhigh
  • 14,652
  • 2
  • 44
  • 75