型安全なprintf -- 如何にしてコンパイル時に型エラーを検出するか --

2014/03/18にTwitterで発生した謎の流れにより、「コンパイル時に型エラーを検出できる型安全な`printf`を作ろう！」という流れに
C言語の`printf`が型安全でないのは有名な話で、その代わりに`boost::format`があったりして`boost::format`などでは「ある程度」型安全というか、型が違ったら「実行時に」例外が投げられたりとか、ありますよね？
そういうのではなく、「コンパイル時に」型エラーを検出してしまえ！　的な感じの`printf`を作成しよう！　という流れになりました
と、言うわけで今回はC++14(C++11縛りはキツかった)で作成した、というか作成中、というより漸進的に作成されるType safe printfのお話です
まず最初に、リポジトリはこちらです
https://github.com/minamiyama1994/Type-safe-printf
現在`char`、`int`、`double`、`char[N]`に対応しています
例えばこんなコードを書くと

```cpp
#include"TSP/TSP.hpp"
auto main ( ) -> int
{
	TSP_PRINTF ( "%c\n" , 'a' ) ;
	TSP_PRINTF ( "%s\n" , "hoge" ) ;
	TSP_PRINTF ( "%d\n" , 1 ) ;
	TSP_PRINTF ( "%f\n" , 0.1 ) ;
	TSP_PRINTF ( "%%\n%c\n%s\n%d\n%f\nHello! TSP!\n" , 'e' , "piyo" , 1 , 0.25 ) ;
//	TSP_PRINTF ( "%f\n" , "hoge" ) ; // <- Compile Error
}
```

こんな実行結果になります

```
a
hoge
1
0.1
%
e
piyo
1
0.25
Hello! TSP!
```

コメントアウトしてある場所が気になりますね……ちょっと外してみましょう

```cpp
#include"TSP/TSP.hpp"
auto main ( ) -> int
{
	TSP_PRINTF ( "%c\n" , 'a' ) ;
	TSP_PRINTF ( "%s\n" , "hoge" ) ;
	TSP_PRINTF ( "%d\n" , 1 ) ;
	TSP_PRINTF ( "%f\n" , 0.1 ) ;
	TSP_PRINTF ( "%%\n%c\n%s\n%d\n%f\nHello! TSP!\n" , 'e' , "piyo" , 1 , 0.25 ) ;
	TSP_PRINTF ( "%f\n" , "hoge" ) ; // <- Compile Error
}
```

こんな実行結果になります、というかコンパイルエラーメッセージが出ます

```
In file included from example.cpp:1:0:
./include/TSP/TSP.hpp: In instantiation of 'constexpr auto tsp::printf(const T& ...) [with format_type = ftmp::list<ftmp::integral<char, '%'>, ftmp::integral<char, 'f'>, ftmp::integral<char, '\012'> >; T = {char [5]}]':
example.cpp:9:2:   required from here
./include/TSP/TSP.hpp:255:55: error: no matching function for call to 'tsp::detail::printf<ftmp::list<ftmp::integral<char, '%'>, ftmp::integral<char,'f'>, ftmp::integral<char, '\012'> > >::func(const char [5])'
   return detail::printf < format_type >::func ( v ... ) ;
                                                       ^
./include/TSP/TSP.hpp:255:55: note: candidate is:
./include/TSP/TSP.hpp:246:26: note: template<class ... T> static constexpr auto tsp::detail::printf_impl_format<ftmp::list<ftmp::integral<char, '%'>,ftmp::integral<char, 'f'>, ftmp::integral<char, ch>...> >::func(double, const T& ...) [with T = {T ...}; char ...ch = {'\012'}]
    constexpr static auto func ( double h , const T & ... v )
                          ^
./include/TSP/TSP.hpp:246:26: note:   template argument deduction/substitution failed:
./include/TSP/TSP.hpp:255:55: note:   cannot convert 'v#0' (type 'const char [5]') to type 'double'
   return detail::printf < format_type >::func ( v ... ) ;
                                                       ^
example.cpp: In function 'int main()':
./include/TSP/TSP.hpp:261:72: error: void value not ignored as it ought to be
  tsp::printf < SPROUT_PP_CAT ( format_type , __LINE__ )> ( __VA_ARGS__ ) ( )
                                                                        ^
example.cpp:9:2: note: in expansion of macro 'TSP_PRINTF'
  TSP_PRINTF ( "%f\n" , "hoge" ) ; // <- Compile Error
  ^
make: *** [all] Error 1
```

これは、`%f`という書式指定子に対して`char[N]`、つまり`char`配列が渡されており、型が合っていないからです
と、これだけ書いたら「C++こわっ意味わからん」となる人が大半だと思うので、少々説明を(説明かったるいという人はリポジトリのソースコード直接見て下さい)

使用ライブラリは[中３女子氏](https://twitter.com/bolero_MURAKAMI)の作成された[Sprout](https://github.com/bolero-MURAKAMI/Sprout)と僕の作った[FTMP](https://github.com/minamiyama1994/FTMP)です
それぞれconstexpr用のライブラリとTMP用のライブラリです

まず、format文字列は`std::basic_string`などではなく`sprout::basic_string`を使用しています
これにより、コンパイル時に「どのような文字列なのか」を取得することが可能となります
更に、この[中３女子氏](https://twitter.com/bolero_MURAKAMI)の作成された[Sprout](https://github.com/bolero-MURAKAMI/Sprout)には`SPROUT_TYPES_STRING_TYPEDEF`という最強マクロがありまして、これを使うことで`sprout::basic_string`の内部を型として得ることが出来ます
どういうこっちゃ！　という人は[こちら](http://melpon.org/wandbox/permlink/7xDz4KVcK9OCDvEC)を御覧ください。要するに「コンパイル時に型レベルで文字列操作が可能になる」ということです
こうなればもうこっちのものです、format文字列がコンパイル時に型として得られたのですから、それを元に可変長引数部分と突き合わせ、整合性がとれているかどうかをチェックするだけです
一つ注意が必要なのが、ここでは`std::cout`などの大半の標準ライブラリは使えません、何故かと言うと処理の大半が`constexpr`関数でなされ、そしてそういったライブラリは`constexpr`関数内では使えないためです
というわけで、直接出力するわけには行きません
そこで、「`constexpr`として構築できるけど非`constexpr`な`operator()`を呼び出したら出力が行われるクラス」を作り、それを結果として返すようにします。Haskellの`IO Monad`が分かる人は、それをイメージしてもらえるとわかりやすいかもしれません
ここまでの流れをまとめると

1. `sprout::basic_string`を元にformat引数を型に変換して取得する
1. format指定と可変長引数部分を突き合わせる
1. 出力を行う関数オブジェクト(これそのものは`constexpr`として構築可能)
1. 結果の関数オブジェクトの`operator()`を呼び出し、出力が完了

という流れです
ここまでの流れは手で書くと煩雑なので`TSP_PRINTF`というマクロにまとめています
概ね、以上です

---

……と、以上の記事は寝る前にサクッと書いたものなのですが、起きてから気がついた、これ、format文字列を型に落とし込めた時点でもう`constexpr`にする意味はもうないのでは……？
……い、いや、`constexpr`だからって誰かが損するわけでもないし！　別にいいし！

では