Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to branch/switch on multiple conditions?

Tags:

c++

c++11

Is there a way to branch on multiple condition without writing code that looks like a mess? Syntaxic sugar in C++11 or C++14 would be appreciated.

#include <iostream>

enum state
{
    STATE_1,
    STATE_2,
    STATE_3,
    STATE_4,
    STATE_5,
    STATE_6,
    STATE_7,
    STATE_8,
};

state f(int a, bool b, const std::string& str)
{
    // How not to:
    if (a < 0)
    {
        if (b == false)
        {
            if (str != "morning")
            {
                return STATE_1;
            }
            else
            {
                return STATE_2;
            }
        }
        else
        {
            if (str != "morning")
            {
                return STATE_3;
            }
            else
            {
                return STATE_4;
            }
        }
    }
    else // a >= 0
    {
        if (b == false)
        {
            if (str != "morning")
            {
                return STATE_5;
            }
            else
            {
                return STATE_6;
            }
        }
        else
        {
            if (str != "morning")
            {
                return STATE_7;
            }
            else
            {
                return STATE_8;
            }
        }
    }
}

int main()
{
    std::cout << "State: " << f(1, true, "morning") << std::endl;
}
like image 422
YSC Avatar asked Dec 22 '15 11:12

YSC


People also ask

Can Switch case have multiple conditions?

A switch statement includes literal value or is expression based. A switch statement includes multiple cases that include code blocks to execute. A break keyword is used to stop the execution of case block. A switch case can be combined to execute same code block for multiple cases.

Can Switch case have multiple conditions C++?

Switch case statement is used when we have multiple conditions and we need to perform different action based on the condition. When we have multiple conditions and we need to execute a block of statements when a particular condition is satisfied.


5 Answers

One could embed a list of boolean (condition results) in a POD at compile-time and switch on it.

Usage: main.cpp

#include <iostream> /* std::cout */
#include "mswitch.h" /* mswitch, mcase */

enum state
{
    STATE_1,
    STATE_2,
    STATE_3,
    STATE_4,
    STATE_5,
    STATE_6,
    STATE_7,
    STATE_8,
};

state f(int a, bool b, const std::string& str)
{
    mswitch(a >= 0, b == true, str == "morning")
    {
        mcase(false, false, false): return STATE_1;
        mcase(false, false, true) : return STATE_2;
        mcase(false, true, false) : return STATE_3;
        mcase(false, true, true)  : return STATE_4;
        mcase(true, false, false) : return STATE_5;
        mcase(true, false, true)  : return STATE_6;
        mcase(true, true, false)  : return STATE_7;
        mcase(true, true, true)   : return STATE_8;
    }
    return STATE_1;
}

int main()
{
    std::cout << "State: " << f(1, true, "morning") << std::endl;
}

Syntaxic sugar: mswitch.h

#ifndef MSWITCH_GUARD_H
#define MSWITCH_GUARD_H

#include <initializer_list>
#include <cstddef>

namespace mswitch
{
    constexpr long long encode(long long value, size_t size) { return value << 6 | (0x3F & size); }

    class mswitch
    {
        std::initializer_list<bool> _flags;
    public:
        mswitch(std::initializer_list<bool> const& l) : _flags(l) {}
        operator long long() const
        {
            long long result = 0;
            size_t index = 0;
            for (bool b : _flags) {
                result |= b << index++;
            }
            return encode(result, _flags.size());
        }
    };

    template<bool head, bool... tail>
    struct mcase
    {
        constexpr mcase() = default;
        constexpr operator long long() const
        {
            return encode(tll(), 1+sizeof...(tail));
        }
        constexpr long long tll() const { return head | mcase<tail...>().tll() << 1; }
    };

    template<bool b>
    struct mcase<b>
    {
        constexpr mcase() = default;
        constexpr operator long long() const { return encode(tll(), 1); }
        constexpr long long tll() const { return b; }
    };
}

#define mswitch(head, ...) switch(mswitch::mswitch{head, __VA_ARGS__})
#define mcase(head, ...) case mswitch::mcase<head, __VA_ARGS__>()

#endif // MSWITCH_GUARD_H

Compile with g++ -std=c++14 -O2 -Wall -pedantic main.cpp

How it works

The mswitch and mcase objects simply build (at compile-time if possible, using constexpr functions) a bijection between a boolean list and a switchable long long. Since mcases are given compile-time constants, all switch labels are in fact contiguous compile-time constant themselves.

like image 63
YSC Avatar answered Nov 08 '22 03:11

YSC


I would make a look-up table for this:

#include <iostream>
#include <string>

enum state {
  STATE_1,
  STATE_2,
  STATE_3,
  STATE_4,
  STATE_5,
  STATE_6,
  STATE_7,
  STATE_8,
};

state f(int a, bool b, const std::string& str) {
  static const state table[2][2][2] = {
    STATE_8, // 0, 0, 0
    STATE_7, // 0, 0, 1
    STATE_6, // 0, 1, 0
    STATE_5, // 0, 1, 1
    STATE_4, // 1, 0, 0
    STATE_3, // 1, 0, 1
    STATE_2, // 1, 1, 0
    STATE_1  // 1, 1, 1
  };
  return table[a < 0][b == false][str != "morning"];
}

int main() {
  std::cout << f(1, true, "morning") << std::endl;
}
like image 27
Lingxi Avatar answered Nov 08 '22 05:11

Lingxi


I agree, pattern matching is a very good fit there. Unfortunately, built-in switch is very limited in C++.

There is pretty straightforward implementation of compile time boolean pack.

#include <type_traits>

namespace detail
{
    constexpr std::size_t pack_bool(std::size_t result)
    {
        return result;
    }

    template<typename T, typename... Ts>
    constexpr std::size_t pack_bool(std::size_t result, T arg, Ts... args)
    {
        static_assert(std::is_same<bool, T>::value, "boolean expected");
        return pack_bool((result << 1) | arg, args...);
    }
}

template<typename T, typename... Ts>
constexpr std::size_t pack_bool(T arg, Ts... args)
{
    static_assert(std::is_same<bool, T>::value, "boolean expected");
    return detail::pack_bool(arg, args...);
}

Now, you can use it in switch statement

#include <iostream>

enum state
{
    STATE_1,
    STATE_2,
    STATE_3,
    STATE_4,
    STATE_5,
    STATE_6,
    STATE_7,
    STATE_8,
};

state f(int a, bool b, const std::string& str)
{
    switch (pack_bool(a >= 0, b == true, str == "morning"))
    {
        case pack_bool(false, false, false) : return STATE_1;
        case pack_bool(false, false, true)  : return STATE_2;
        case pack_bool(false, true,  false) : return STATE_3;
        case pack_bool(false, true,  true)  : return STATE_4;
        case pack_bool(true,  false, false) : return STATE_5;
        case pack_bool(true,  false, true)  : return STATE_6;
        case pack_bool(true,  true,  false) : return STATE_7;
        case pack_bool(true,  true,  true)  : return STATE_8;
    }
    return STATE_1;
}

int main()
{
    std::cout << "State: " << f(1, true, "morning") << std::endl;
}
like image 31
Stas Avatar answered Nov 08 '22 05:11

Stas


here's my version:

Features:

  1. Preserves compiler checks for missing cases and gives informative messages about which cases were missed.

  2. Compile-time evaluation of cases means zero runtime overhead

  3. No macros to pollute the global namespace and randomly prevent header-only libraries from working :-)

drawbacks:

  1. Requirement to predefine some boilerplate enums (once only, in the library which I have done for you)

Code:

#include <iostream>
#include <utility>
#include <sstream>
#include <string>

namespace detail{

    template<size_t N> struct boolean_value;
    template<size_t N> using boolean_value_t = typename boolean_value<N>::type;
    template<size_t N> constexpr auto to_int(boolean_value_t<N> b) { return static_cast<int>(b); };
    template<size_t N> constexpr auto to_boolean_value(int i) { return static_cast<boolean_value_t<N>>(i); };

    template<> struct boolean_value<1> {
        enum type { bit0, bit1 };
    };

    template<> struct boolean_value<2> {
        enum type { bit00, bit01, bit10, bit11 };
    };

    template<> struct boolean_value<3> {
        enum type { bit000, bit001, bit010, bit011, bit100, bit101, bit110, bit111 };
    };

    template<class...Args, size_t...Is>
    static constexpr auto make_bitfield(std::tuple<Args...> t, std::index_sequence<Is...>)
    {
#if __cplusplus > 201402L
        int accum = (0 | ... | (std::get<Is>(t) ? (1 << Is) : 0));
#else
        int accum = 0;
        using expand = int[];
        (void) expand { (std::get<Is>(t) ? accum |= (1 << Is) : 0) ... };
#endif
        return to_boolean_value<sizeof...(Is)>(accum);
    }

}

template<class...Args>
constexpr
auto mcase(Args&&...args)
{
    return detail::make_bitfield(std::make_tuple(bool(std::forward<Args>(args))...),
                                 std::index_sequence_for<Args...>());
}

// little function to defeat the optimiser, otherwise clang inlines the whole program!
auto get_result()
{
    using namespace std;

    istringstream ss("foo 2");
    auto result = tuple<string, int>();
    ss >> get<0>(result) >> get<1>(result);
    return result;
}

int main()
{
    using namespace std;
    const auto result = get_result();
    const auto& s1 = std::get<0>(result);
    const auto& v1 = std::get<1>(result);

    switch(mcase(s1 == "foo"s, v1 == 2))
    {
        case mcase(true, true):
            cout << mcase(true, true) << endl;
            break;

        case mcase(false, false):
            cout << mcase(false, false) << endl;
            break;
    }
    return 0;
}

Example Compiler Output:

./mswitch.cpp:114:12: warning: enumeration values 'bit01' and 'bit10' not handled in switch [-Wswitch]
    switch(mcase(s1 == "foo"s, v1 == 2))
           ^
1 warning generated.
./mswitch.cpp:114:12: warning: enumeration values 'bit01' and 'bit10' not handled in switch [-Wswitch]
    switch(mcase(s1 == "foo"s, v1 == 2))
           ^
1 warning generated.

Runtime Output:

3
like image 28
Richard Hodges Avatar answered Nov 08 '22 04:11

Richard Hodges


As an alternate answer using basic functionalities of C++, you can also consider using ternary operators.

enum state
{
    STATE_1,
    STATE_2,
    STATE_3,
    STATE_4,
    STATE_5,
    STATE_6,
    STATE_7,
    STATE_8,
};

state f(int a, bool b, const std::string& str)
{
    // How not to:
    if (a < 0)
        return b == true ? (str == "morning" ? STATE_4 : STATE_3) : (str == "morning" ? STATE_2 : STATE_1);
    else // a >= 0
        return b == true ? (str == "morning" ? STATE_8 : STATE_7) : (str == "morning" ? STATE_6 : STATE_5);
}

int main()
{
    std::cout << "State: " << f(1, true, "morning") << std::endl;
}

The result is pretty compact while using only basic operators.

like image 35
Ian Avatar answered Nov 08 '22 03:11

Ian