Skip to content

Instantly share code, notes, and snippets.

@cjsmeele
Created November 13, 2017 00:35
Show Gist options
  • Save cjsmeele/92ddbc8b342838aba068d07ceb918c1c to your computer and use it in GitHub Desktop.
Save cjsmeele/92ddbc8b342838aba068d07ceb918c1c to your computer and use it in GitHub Desktop.
C++-ish sort-of switch statement reimplementation.
/**
* \file
* \brief C++-ish sort-of switch statement reimplementation.
* \author Chris Smeele
*
* Copyright (c) 2017, Chris Smeele
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <tuple>
#include <type_traits>
// Usage: match(value, [test_value, code]...)
// test_value can be one of:
// - a bool, runs the code if true
// - an x,y pair, runs code if x <= value <= y
// - something that can be ==-compared to `value`, runs code if they are equal
// - a callable that receives `value`, runs code if result is true
//
// The result of `code` is used to determine what to do next:
// - If the code returns MatchAction::satisfy, the match is exited
// - If the code returns MatchAction::next, the match continues with the next check
// - If the code returns MatchAction::fallthrough, the match immediately continues
// by executing the next code block, ignoring its test_value.
// - If the code returns a boolean, true means satisfy, false means next.
// - If the code does _not_ return any value, MatchAction::satisfy is implied,
// so it exits the match.
//
// Instead of a callable, you can put one of the MatchAction values into the place of `code`.
//
// The following match:
//
// match(rand() % 10,
// [](int n) { return n&1; }, [] { std::cout << "odd number\n"; return match_next; }, // catches any odd number, but continues checking other cases
// 0, [] { std::cout << "0\n"; }, // match_satisfy is implied
// 3, [] { std::cout << "3\n"; return match_next; }, // could also be fallthrough
// std::pair(1,4), [] { std::cout << "1-4\n"; return match_fallthrough; },
// 8, [] { std::cout << "1-4 or 8\n"; }, // match_satisfy is implied
// true, [] { std::cout << "number was not interesting enough\n"; }); // fires for 5,6,7,9
//
// Has the following C switch equivalent:
//
// int n = rand() % 10;
// bool uninteresting = false;
// if (n & 1) { std::cout << "odd number\n"; uninteresting = true; }
// switch(n) {
// case 0: std::cout << "0\n"; break;
// case 3: std::cout << "3\n"; uninteresting = false;
// case 1:
// case 2:
// case 4: std::cout << "1-4\n";
// case 8: std::cout << "1-4 or 8\n"; uninteresting = false; break;
// default: uninteresting = true;
// }
// if (uninteresting)
// std::cout << "number was not interesting enough\n";
//
//
// The equivalent of the switch `default` case is a `true` test value at the end of the match.
// If for a given value none of the cases returns MatchAction::satisfy, this `true` case will be executed.
// Simsalabim.
template<class...> using void_t = void;
// Is T1 == T2 valid?
template<typename T1, typename T2, typename=void>
struct is_equality_comparable : std::false_type { };
template<typename T1, typename T2>
struct is_equality_comparable<T1,T2,void_t<decltype(std::declval<T1>() == std::declval<T2>())>> : std::true_type { };
// Is T1 <= T2 valid?
template<typename T1, typename T2, typename=void>
struct is_lte_comparable : std::false_type { };
template<typename T1, typename T2>
struct is_lte_comparable<T1,T2,void_t<decltype(std::declval<T1>() <= std::declval<T2>())>> : std::true_type { };
// Are P::first <= T and T <= P::second valid?
template<typename T, typename P, typename=void>
struct is_pair_of_lte2_comparable : std::false_type { };
template<typename T, typename P>
struct is_pair_of_lte2_comparable<T,P,void_t<decltype(std::declval<decltype(P::first)>() <= std::declval<T>()),
decltype(std::declval<T>() <= std::declval<decltype(P::second)>())>> : std::true_type { };
enum class MatchAction {
satisfy = 0,
next,
fallthrough,
};
constexpr auto match_satisfy = MatchAction::satisfy;
constexpr auto match_next = MatchAction::next;
constexpr auto match_fallthrough = MatchAction::fallthrough;
template<typename T>
constexpr void match_impl(const T &x, MatchAction) {
return (void)x;
}
template<typename T, typename V1, typename F1, typename... R>
constexpr void match_impl(const T &x, MatchAction ma, const V1 &v1, F1 f1, R... rest) {
// Checking on bools doesn't really make sense.
// Reserve bools for an always-match case.
static_assert(!std::is_same<T,bool>::value, "For boolean matches, use if/else etc. instead.");
MatchAction action = MatchAction::next;
// The case body.
auto ff = [&] {
if constexpr (std::is_same<MatchAction,F1>::value)
return [&] { return f1; };
else if constexpr (std::is_same<MatchAction,decltype(f1())>::value)
return f1;
else if constexpr (std::is_same<bool,decltype(f1())>::value)
return [&] { return f1() ? match_satisfy : match_next; };
else
return [&] { f1(); return match_satisfy; };
}();
if (ma == MatchAction::fallthrough) {
// The previous case did a fallthrough, skip the check.
action = ff();
} else if (ma == MatchAction::next) {
// The previous case did not match or was not satisfactory.
if constexpr (std::is_same<V1,bool>::value) {
if (v1)
action = ff();
} else if constexpr (is_pair_of_lte2_comparable<T,V1>::value) {
if (v1.first <= x && x <= v1.second)
action = ff();
} else if constexpr (is_equality_comparable<T,V1>::value) {
if (x == v1)
action = ff();
} else {
if (v1(x))
action = ff();
}
}
if (action == MatchAction::satisfy)
return;
else
match_impl(x, action, rest...);
}
template<typename T, typename... R>
constexpr void match(const T &x, R... rest) {
match_impl(x, match_next, rest...);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment