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;
}
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.
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.
One could embed a list of boolean (condition results) in a POD at compile-time and switch
on it.
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;
}
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
The mswitch
and mcase
objects simply build (at compile-time if possible, using constexpr
functions) a bijection between a boolean list and a switch
able long long
. Since mcase
s are given compile-time constants, all switch
labels are in fact contiguous compile-time constant themselves.
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;
}
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;
}
here's my version:
Preserves compiler checks for missing cases and gives informative messages about which cases were missed.
Compile-time evaluation of cases means zero runtime overhead
No macros to pollute the global namespace and randomly prevent header-only libraries from working :-)
#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;
}
./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.
3
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With