はじめに
※ ネタです
PHP の match 式 を読んでいたら、何やら臭うコードが・・・
function fizzbuzz($num) {
print match (0) {
$num % 15 => "FizzBuzz" . PHP_EOL,
$num % 3 => "Fizz" . PHP_EOL,
$num % 5 => "Buzz" . PHP_EOL,
default => $num . PHP_EOL,
};
}
この match (0)
部分、なんか気持ち悪くないですか?
$num
の値に応じて処理を分岐させたいので、ここでは match ($num)
と書けるのが美しいと感じました。
この気持ち、C++ にぶつけよう!
C++
ご存知 C++ には演算子オーバーロードがあり、言語の許す限り任意の構文っぽいものを生み出せます。
これが C++ の match
式だ!
case の条件部分に match (n)
の n
を引数としたラムダ式を書けるところがポイントです。
for (unsigned n = 1; n <= 20; ++n)
{
auto const s = match (n) [
(arg % 15 == 0) --> "FizzBuzz",
(arg % 3 == 0) --> "Fizz",
(arg % 5 == 0) --> "Buzz",
default_ --> std::to_string(n)
];
std::cout << s << std::endl;
}
コード
グローバルに制約のない演算子オーバーロードを配置するなど、汎用性は一切考慮されていないものです
#include <iostream>
#include <string>
#include <tuple>
template <typename L, typename R>
struct modulo_expression
{
L lhs;
R rhs;
template <typename A>
auto eval(A arg) const
{
return lhs.eval(arg) % rhs.eval(arg);
}
};
template <typename L, typename R>
struct equals_expression
{
L lhs;
R rhs;
template <typename A>
auto eval(A arg) const
{
return lhs.eval(arg) == rhs.eval(arg);
}
};
template <typename T>
struct identity_expression
{
T value;
template <typename A>
T eval(A) const
{
return value;
}
};
struct argument_expression
{
template <typename A>
A eval(A arg) const
{
return arg;
}
};
argument_expression const arg;
template <typename L, typename R>
modulo_expression<L, identity_expression<R>> operator % (L lhs, R rhs)
{
return { lhs, { rhs } };
}
template <typename L, typename R>
equals_expression<L, identity_expression<R>> operator == (L lhs, R rhs)
{
return { lhs, { rhs } };
}
template <typename E>
struct pattern_expression
{
E expression;
template <typename A>
auto eval(A arg) const
{
return expression.eval(arg);
}
};
template <typename E>
pattern_expression<E> operator -- (E expression, int)
{
return { expression };
}
template <typename P, typename T, typename N>
struct case_expression
{
P pattern;
T value;
N next;
template <typename A>
std::tuple<bool, T> eval(A arg) const
{
auto const t = next.eval(arg);
if (std::get<0>(t))
{
return t;
}
else
{
return std::make_tuple(pattern.eval(arg), value);
}
}
};
template <typename P, typename T>
struct case_expression<P, T, void>
{
P pattern;
T value;
template <typename A>
std::tuple<bool, T> eval(A arg) const
{
return std::make_tuple(pattern.eval(arg), value);
}
};
template <typename P, typename T>
case_expression<P, T, void> operator > (P pattern, T value)
{
return { pattern, value };
}
template <typename P1, typename T1, typename N1, typename P2, typename T2>
case_expression<P2, T2, case_expression<P1, T1, N1>> operator , (case_expression<P1, T1, N1> lhs, case_expression<P2, T2, void> rhs)
{
return { rhs.pattern, rhs.value, lhs };
}
struct default_pattern_expression
{
template <typename A>
auto eval(A) const
{
return true;
}
};
default_pattern_expression const default_{};
template <typename S>
struct match_expression
{
S scrutinee;
template <typename C>
auto operator [] (C cases) const
{
return std::get<1>(cases.eval(scrutinee));
}
};
template <typename S>
match_expression<S> match(S scrutinee)
{
return { scrutinee };
}
int main(void)
{
for (unsigned n = 1; n <= 20; ++n)
{
auto const s = match(n) [
(arg % 15 == 0) --> "FizzBuzz",
(arg % 3 == 0) --> "Fizz",
(arg % 5 == 0) --> "Buzz",
default_ --> std::to_string(n)
];
std::cout << s << std::endl;
}
return 0;
}
おわりに
数年ぶりに書きましたが、やっぱ C++ は楽しいですね〜
今回 std::to_string
の存在を知りました。便利になりましたね。