可変個引数テンプレート
書式指定して出力できるログクラスを作りたい
導入
以前作ったログクラスをprintfのように書式指定して利用できるようにしたい。
それを実現する方法を調査したところ、可変個引数テンプレートを利用するのがよさそうだと分かった。
ここでは、可変個引数テンプレートを学習して、ログクラスに導入するところまでを目標とする。
printf()について
printf()は個数と型の両方が可変である引数を受け取る典型的な関数。
ただし、ユーザ定義型には対応できない。また、型安全でもないらしい(どゆこと?)
printf()の先頭引数は書式文字列といわれる。それ以降の引数は書式文字列に応じて可変個の値をとる。
// 正しい printf("The value of %s is %g\n", "x", 3.14); // string型やユーザ定義のPoint型には対応できない std::string name = "target"; printf("The value of %s is %P\n", name, Point{34, 200}); // %sにint型の値を渡してるし、%gに値を渡せてない printf("The value of %s is %g\n", 7);
可変個引数テンプレート
可変個引数テンプレートを用いると拡張性があり型安全なprintf()の変種を実装できる。
printf()関数っぽいものを実装してみる
#include <iostream> #include <stdexcept> // 引数が書式文字列一つのケースのPrintf関数 // 書式指定子が含まれる場合は例外を送出する void Printf(const char* s) { if (s == nullptr) return; while (*s) { if (*s == '%' && *++s != '%') throw std::runtime_error("invalid format: missing arguments"); std::cout << *s++; } } // 書式文字列と書式引数があるケースのPrintf関数 // 可変個引数テンプレートを利用 // template<typename T, typename... Args> void Printf(const char* s, T value, Args... args) { while (s && *s) { if (*s == '%' && *++s != '%') { std::cout << value; printf(++s, args...); std::cout << std::endl; // 最後に改行する return; } std::cout << *s++; } throw std::runtime_error("extra arguments provided to printf"); } int main() { Printf("Hello %s.", "C++"); Printf("%d, %d, %d", 1, 2, 3); return 0; }