daiki8は片付けができない

片付けしないと

可変個引数テンプレート

書式指定して出力できるログクラスを作りたい

導入

以前作ったログクラスを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;
}