Created
November 13, 2017 00:35
-
-
Save cjsmeele/92ddbc8b342838aba068d07ceb918c1c to your computer and use it in GitHub Desktop.
C++-ish sort-of switch statement reimplementation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* \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