C++ で競プロ用の Print 作ったよ

4 min read

こんにちは、atreeです。ちょっと Zenn も使ってみたくなったのでテストということではてなブログから引用しています。

ところでそこの C++ で競プロをしているあなた!std::coutでの出力めんどくさくないですか?<<とか打ちにくいですよね。そういうわけで Print 構造体(関数オブジェクトというやつですか?)を作ったので解説していきます。

#include <iostream>
#include <vector>
#include <experimental/iterator>
using namespace std;

struct Print {
    ostream &os;
    const string sep;
    bool is_debug;
    Print(ostream &os, string sep, bool is_debug): os(os), sep(sep), is_debug(is_debug) {}
    void operator()(bool) { os << "\n"; }
    template<class Head, class... Tail> void operator()(bool is_first, Head &&head, Tail &&...tail) {
        os << head;
        if (not is_first and sizeof...(tail) != 0) os << sep;
        operator()(false, forward<Tail>(tail)...);
    }
    template<class T> void operator()(bool, vector<T> &vec) {
        copy(cbegin(vec), cend(vec), experimental::make_ostream_joiner(os, sep));
        os << "\n";
    }
    template<class T> void operator()(bool, vector<vector<T>> &vv) {
        size_t ind = 0;
        if (is_debug) os << "\n";
        for (auto &vec: vv) {
            if (is_debug) os << ind++ << ": ";
            operator()(false, vec);
        }
    }
};

auto print_impl = Print(cout, " ", false);
#define print(...) print_impl(true, "", __VA_ARGS__)
#ifdef LOCAL
auto debug_impl = Print(cerr, " ", true);
    #define debug(...) debug_impl(true, "(" #__VA_ARGS__ ") "s, __VA_ARGS__)
#else
    #define debug(...)
#endif

int main() {
    int a = 1;
    vector<int> arr{1, 2, 3, 2};
    vector<string> str_arr{"Hello", "World"};
    vector<vector<int>> table{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    vector<vector<vector<int>>> test{{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}},
                                     {{1, 1, 1}, {2, 2, 2}, {3, 3, 3}}};
    print(table);
    print(arr);
    print(str_arr);
    print("Test");
    print(a, 3.14, "Hello");
    print(test);

    debug(table);
    debug(arr);
    debug(str_arr);
    debug("Test");
    debug(a, 3.14, "Hello");
    debug(test);
    debug(a+1+10);
}

標準出力:

1 2 3
4 5 6
7 8 9
1 2 3 2
Hello World
Test
1 3.14 Hello
1 2 3
4 5 6
7 8 9
1 1 1
2 2 2
3 3 3

標準エラー出力

(table)
0: 1 2 3
1: 4 5 6
2: 7 8 9
(arr) 1 2 3 2
(str_arr) Hello World
("Test") Test
(a, 3.14, "Hello") 1 3.14 Hello
(test)
0:
0: 1 2 3
1: 4 5 6
2: 7 8 9
1:
0: 1 1 1
1: 2 2 2
2: 3 3 3
(a+1+10) 12

解説

特に C++ に詳しいわけではないのであっさりかつ間違いがあるかもしれません。
関数オブジェクトにすることで、セパレータやstd::ostream、デバッグ出力かどうかなどをメンバ変数で持ってprint(...)debug(...)をいい感じにできます。可変長引数になっているので複数を一行に出力することもできます。また、デバッグ情報を渡すためにマクロの#xで文字列xを得る手法を利用しています。(noshi91 さんに教えていただきました、Twitterで情報提供してくださった御三方ありがとうございました。)この時に知ったんですけど、C++ って"a""b""c"が勝手に"abc"になるんですね。謎仕様だ... ちなみに std::vector<T>printする場合もデバッグ情報としてから文字列を渡しているため、is_firstのくだりがないと頭に空白が入ってしまいます。 このために、可変長マクロに最初に入った時のみis_firsttrue、それ以降はfalseになるようにしています。あとはexperimental::make_ostream_joinerですね。見たまんまです。参考にさせていただいたブログを見てください...

おわりに

std::vectorにしか対応してないのはお気持ちですね。std::pairとかまでやるかは微妙ですが(とか思ったけどstd::mapはあってもいいな)、せっかくexperimental::make_ostream_joinerを使ってるのでこれが使えるstd::set,std::dequeとかには対応していきたいですね。(void operator()(bool, vector<T> &vec)void operator()(bool, T &vec)にすると比較的それっぽい挙動になるがstd::stringでバグる、「std::setorstd::dequeorstd::vector」を判定する†メタ関数†を書けば良さそうだけどわからないのでとりあえず保留。有識者教えてください!)

なにはともあれ最後まで読んでいただきありがとうございました〜!typo や間違いなどありましたら教えて欲しいです!!

参考

https://qiita.com/Lily0727K/items/06cb1d6da8a436369eed
https://koturn.hatenablog.com/entry/2018/07/22/120000
https://twitter.com/noshi91/status/1407263189415333889?s=20

編集履歴

  • 2021.07.10 Hetena から引用して公開