前回のコードは、既にMPLの素晴らしい恩恵を受けていたわけだが、やはり他人にとっては読みにくいだろう。なにが分かりにくいかといって、メタ関数、FizzBuzzSequence の引数が、3の倍数か、5の倍数か、あるいは3と5両方の倍数なのかを判断する部分が分かりにくい。最初はMPLのpush_backの中に書いたのだが、これでは他人が読みづらかろうと、外に押し出して、メタデータ、elem を定義した。しかし、それでもまだ読みづらい。
ここで気付いたのは、そもそも、3の倍数かどうかを判断する条件式自体が、読みづらいということだ。(x%3 == 0)なんて分かりにくすぎる。既にこれはメタプログラミングの問題ではない。こういう時、我々はどうするかというと、コメントを付け加えるか、別の関数を作るのだ。では早速。
#include <iostream>
#define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS
#define BOOST_MPL_LIMIT_VECTOR_SIZE 100
#include <boost/mpl/vector.hpp>
#include <boost/cstdint.hpp>
#include <boost/type_traits.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/push_back.hpp>
#include <boost/mpl/for_each.hpp>
namespace mpl = boost::mpl ;
struct Fizz{ static char * const value ; } ;
struct Buzz{ static char * const value ; } ;
struct FizzBuzz{static char *const value ; } ;
char * const Fizz::value = "Fizz" ;
char * const Buzz::value = "Buzz" ;
char * const FizzBuzz::value = "FizzBuzz" ;
template < int i >
struct GetFizzBuzzElement
{
typedef typename
// if FizzBuzz
mpl::eval_if_c< (i % 3 == 0) && (i % 5 == 0)
, mpl::identity< FizzBuzz >
// if Fizz
, mpl::eval_if_c< (i % 3 == 0)
, mpl::identity< Fizz >
// if Buzz
, mpl::eval_if_c< (i % 5 == 0)
, mpl::identity< Buzz >
// else
, mpl::int_< i >
>// Buzz
>// Fizz
>::type type ;
} ;
template< int i >
struct FizzBuzzSequence
{
typedef typename
mpl::push_back<
typename FizzBuzzSequence< i - 1 >::type
, typename GetFizzBuzzElement<i>::type
>::type type ;
} ;
template < >
struct FizzBuzzSequence<1>
{
typedef mpl::vector< mpl::int_<1> > type ;
} ;
struct print
{
template < typename T >
void operator () (T) const
{
std::cout << T::value << "\n" ;
}
} ;
int main()
{
mpl::for_each< FizzBuzzSequence<100>::type >( print() ) ;
}
このように、FizzBuzz如何を判断するメタ関数、GetFizzBuzzElementを定義すると、FizzBuzzSequence メタ関数が非常に読みやすくなった。そして、GetFizzBuzzElement メタ関数は、分かりやすいインデントと、簡単なコメントを付け加えた。なお、深さだけで見れば、GetFizzBuzzElement メタ関数内のインデントは正しくないのだが、意味で考えれば、if else if else if elseなので、あえてこのようなインデントにしている。
さて、今思うに、Linus Torvaldsは正しかった。詳しい理由は後ほど、別記事で書くことにするが、重要なことは二つ、インデントは8文字で、インデントの深さは3階までである。
追記:
ここでは、メタプログラミングの用語は、Boost.MPLの作者による著書、C++ Template Metaprogrammingに拠った。コンパイル時の関数として、メタ関数、コンパイル時の変数としてメタデータである。もし、Lispを右に、Haskellを左に置くような人には、変数の呼称には、メタデータではなく、値を名前に束縛するといったほうが理解できるかもしれない。
No comments:
Post a Comment