4
\$\begingroup\$

I always considered switch statement as somehow defective:

  • works only on integral types and enumerations.
  • isn't an readability improvement over traditional if/else chain.
  • forget a break - you have a bug.
  • variable declaration spills over neighbouring cases.
  • is essentially a computed goto

Because of said reasons, and also as an exercise on lambdas and variadic templates I created my own flow control function.

#include <functional>
#include <tuple>

template<typename V>
bool switch_on(const V& value)
{
    return false;
}

template<typename V, typename P, typename... Args>
bool switch_on(const V& value, const P& p, Args... args)
{
    if(std::get<0>(p)(value, std::get<1>(p)))
    {
        std::get<2>(p)();
        return true;
    }
    else
    {
        return switch_on(value, args...);
    }
}

template<template <typename> class P, typename F, typename V >
auto case_of(const V& v, const F& p) -> decltype( std::make_tuple(P<V>(), v, p) )
{
    return std::make_tuple(P<V>(), v, p);
}

template<typename P, typename F, typename V >
auto case_of(const V& v, const F& p) -> decltype( std::make_tuple(P(), v, p) )
{
    return std::make_tuple(P(), v, p);
}

template<typename F, typename V >
auto case_of(const V& v, const F& p) -> decltype( std::make_tuple(std::equal_to<V>(), v, p) )
{
    return std::make_tuple(std::equal_to<V>(), v, p);
}

so I can use:

using std::less;
using std::greater;

int main()
{
    int a = 42;
    switch_on(a,
        case_of<less>(0, [&]{
            std::cout << "LESS THAN ZEROOOOOOOOOOOOOO."; 
        }),
        case_of(42, [&]{
            std::cout << "Yes";
        }),
        case_of<greater>(9000, [&]{
            std::cout << "IT'S OVER NINE THOUSAAAAAAAAND!!!";
        })
    );
}

While in Lisp, it is encouraged to create new forms of flow control, what about C++? I would also like to see some opinions about template usage, some pointers on how to improve the code and possible corner cases when this code will break.

\$\endgroup\$
  • \$\begingroup\$ Not sure I agree wiht: isn't an readability improvement over traditional if/else chain. or forget a break - you have a bug lot of functionality for fall through and when you do need it the compiler will warn you about it being missing so not a real problem. Don't believe this is true variable declaration spills over neighboring cases in C++. Anything with a constructor is bound into a case scope. Though true is essentially a computed goto you can use the same argument for for(;;), while(), if(){}else{} etc. Any control flow basically boils down to a computed goto. \$\endgroup\$ – Martin York Jun 5 '13 at 0:58
  • \$\begingroup\$ @LokiAstari when you do need it the compiler will warn you about it being missing so not a real problem. Not sure what you mean here - can compiler distinguish when I need fall-through? Anything with a constructor is bound into a case scope. Well, apparently only if you use braces to introduce one, that is, by is essentially a computed goto I meant goto not on assembler level, but on a high-level language level. And the rant against break is because switch favours less common case (I want to fall-throgh) over more common case. \$\endgroup\$ – milleniumbug Jun 5 '13 at 1:41
  • \$\begingroup\$ @LokiAstari I realise that my approach is flawed though - this function doesn't integrate so well with language, but I'm still looking for alternatives. \$\endgroup\$ – milleniumbug Jun 5 '13 at 1:46
8
\$\begingroup\$

Lisp has an uniform syntax, so it’s possible to create user defined flow control constructs that look exactly like the built in constructs. In C++, that’s generally not possible.

Your code is certainly clever, and it solves the problem of omitted breaks (I consider the other drawbacks you list less important). However:

  • Despite naming this a “switch”, you’ve essentially recreated an if-else chain. Notably, a compiler may have a bit more difficulty optimizing this than a traditional switch.
  • Your implementation does not seem to support range comparisons or a default case, although both of these seem to be doable in your conceptual framework.
  • When used in a disciplined way, falling through from one switch case to the next is not always devoid of merit.
  • I’m not sure that returning this boolean has a great benefit. It might be preferable to design a construct that ensures that one of the cases is always executed.
  • Be honest: do you really find this more readable than a traditional switch statement?
\$\endgroup\$
  • \$\begingroup\$ Thank you for your opinion. Well, at first I tried to copy Pascal's case of statement, and halfway switched to VB's Select Case. So... yeah, maybe it's not that readable. \$\endgroup\$ – milleniumbug Jun 4 '13 at 14:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.