【C++】あらゆる変数をprintするdump関数を本気で作ってみた
はじめに
最近競プロでC++を使い始めたのですが、C++にはあらゆる型の変数がprintできるようなPythonのprint()のような関数がありません。
なので、例えばデバッグのためにvectorの中身をprintしたい!と思っても、いちいちforループを書いて、cout << a[i] << endl;
なんてやらなければなりません。
競プロは時間との戦いですから、このようにタイプ数が多いのは好ましくありませんし、同じ理由で時間のかかるデバッガーもあまり使いたくありません。
もしPythonみたいにprint(変数名1, 変数名2, ...)
とするだけで、あらゆる変数の中身を表示することが出来たら…。
そこで、あらゆる型に対応したC++のダンプ関数を本気で作ってみました!
今回作ったダンプ関数の特徴はこんな感じです。
- 多次元配列や多次元の(多重)マップ、(多重)セット、タプル、複素数やエラーオブジェクトまで、どんな型の変数でも、文字列表現にして標準エラー出力(std::clog)に出力する。
- 行幅が設定でき、自動でインデントを行う。
- 出力は色付きで、カスタマイズ可能。
- ヘッダーオンリーライブラリで、ビルドなどは不要。
- マクロを使うか演算子を定義するとユーザー定義型もダンプできる。
- 変数の文字列表現はJavaScriptとPython, C++のシンタックスに似ている。
使い方
cpp_dump(expr...)
マクロに変数を渡すだけです!
#include <bits/stdc++.h>
#include "path/to/cpp-dump/dump.hpp"
int main() {
std::vector<std::vector<int>> my_vector{{3, 5, 8, 9, 7}, {9, 3, 2, 3, 8}};
cpp_dump(my_vector);
}
このダンプ関数の特徴
多次元配列や多次元の(multi)map、(multi)set、tuple、pair、複素数やエラーオブジェクト等、あらゆる型に対応
対応している型はvectorだけではありません。map, set, tuple, pairなど、競プロで使うであろうありとあらゆる型に対応しています。
#include <bits/stdc++.h>
#include "path/to/cpp-dump/dump.hpp"
int main() {
int my_int = 15;
int *ptr = &my_int;
void *void_ptr = &my_int;
std::vector<std::vector<int>> my_vector{{3, 5, 8, 9, 7}, {9, 3, 2, 3, 8}};
std::set<char> my_set{'A', 'p', 'p', 'l', 'e'};
std::map<int, int> my_map{{2, 6}, {4, 6}, {5, 3}};
std::multiset<char> my_multiset{'A', 'p', 'p', 'l', 'e'};
std::multimap<int, int> my_multimap{{2, 4}, {4, 6}, {5, 3}, {4, 7}};
std::pair<int, char> my_pair{8, 'a'};
std::tuple<int, double, std::string> my_tuple{7, 4.5, "This is a string."};
std::queue<int> my_queue;
std::priority_queue<int> my_priority_queue;
std::stack<int> my_stack;
for (auto v : {1, 2, 3, 4, 5})
my_queue.push(v), my_priority_queue.push(v), my_stack.push(v);
std::bitset<8> my_bitset(0x3a);
std::complex<double> my_complex{1.0, 1.0};
std::optional<int> my_optional{15};
std::variant<int, std::string> my_variant{"1"};
std::vector<std::pair<int, std::string>>
vector_of_pairs{{1, "apple"}, {3, "banana"}};
std::clog << "\n// Basic Type" << std::endl;
cpp_dump(false, 0, 0.0, '0'); cpp_dump(true, 3.14, my_int, 9265);
cpp_dump("This is a string."); cpp_dump(ptr, void_ptr, nullptr);
std::clog << "\n// Container" << std::endl;
cpp_dump(my_vector);
std::clog << "\n// Set/Map" << std::endl;
cpp_dump(my_set); cpp_dump(my_map);
std::clog << "\n// Multiset/Multimap" << std::endl;
cpp_dump(my_multiset); cpp_dump(my_multimap);
std::clog << "\n// Tuple" << std::endl;
cpp_dump(my_tuple); cpp_dump(my_pair);
std::clog << "\n// FIFO/LIFO" << std::endl;
cpp_dump(my_queue); cpp_dump(my_priority_queue); cpp_dump(my_stack);
std::clog << "\n// Other" << std::endl;
cpp_dump(my_bitset); cpp_dump(my_complex);
cpp_dump(my_optional, std::nullopt); cpp_dump(my_variant);
std::clog << "\n// Combination" << std::endl;
cpp_dump(vector_of_pairs);
}
対応する全ての型の一覧はライブラリのREADMEにあります。
設定した行幅に収まるように自動でインデント
#include <bits/stdc++.h>
#include "path/to/cpp-dump/dump.hpp"
int main() {
std::vector my_vector{
"This is a test string.", "This is a test string.", "This is a test string."};
cpp_dump(my_vector);
my_vector.push_back("This is a test string.");
cpp_dump(my_vector);
}
このように要素を追加して行幅を超えそうになると、、
自動でインデントされます!!
ちなみに多次元配列などは最初から改行されます。
行幅の設定は次のように行えます。
CPP_DUMP_SET_OPTION(max_line_width, 行幅の文字数);
デフォルトでは160が設定されています。競プロで提出時に無効化できるようにマクロを作りましたが、普通に代入操作で設定することも可能です。詳しくはリポジトリのREADMEを参照してください。
出力のカラーリングはカスタマイズ可能
例えば下のように色を変えることができます。詳しくはリポジトリのREADMEを参照してください。
マクロを使うことで、自作クラスも対応させることが可能
マクロを使うか演算子を定義することで、自作クラスも対応させることが可能です。
詳しくはリポジトリのREADMEをご覧ください!
CPP_DUMP_DEFINE_DANGEROUS_EXPORT_OBJECT(i, str());
その他にも便利な機能あり
その他、[dump]
の代わりにファイル名や行数を出力したり、表示を変えるマニピュレータなど、便利な機能があります!
詳しくはリポジトリのREADMEをご覧ください!
インストール方法
- リポジトリを好きな場所にダウンロードします。
gitが使える人は
git clone https://github.com/philip82148/cpp-dump
もしくは
git submodule add https://github.com/philip82148/cpp-dump
gitが使えない人は
Releasesからソースフォルダをダウンロードして、好きな場所に解凍しましょう。
- ソースファイル内で、
cpp-dump/dump.hpp
をincludeします。
#include "path/to/cpp-dump/dump.hpp"
ダウンロードしたフォルダ内にあるdump.hpp
というファイルをソースフォルダ内でincludeします。
※パスは自身の環境に合わせて変えてください。
- あとはソースファイル内で関数を呼び出すだけ!
cpp_dump(変数1, 変数2, ...);
// または
cpp_dump::dump(変数1, 変数2, ...);
ヘッダーオンリーライブラリなので、ビルド等は不要です!
競技プログラミングでの便利な使い方
競プロでは、cpp_dump()
と打つのは長いので、マクロでdump()
と短くしてしまいましょう。
#define dump(...) cpp_dump(__VA_ARGS__)
また、提出時にはダンプ関連のコードは消えるようにしておくと便利です。
具体的には、以下のようにします。
#ifdef DEFINED_ONLY_IN_LOCAL
#include "./cpp-dump/dump.hpp"
#define dump(...) cpp_dump(__VA_ARGS__)
namespace cp = cpp_dump;
#else
#define dump(...)
#define CPP_DUMP_SET_OPTION(...)
#define CPP_DUMP_DEFINE_EXPORT_OBJECT(...)
#define CPP_DUMP_DEFINE_EXPORT_ENUM(...)
#define CPP_DUMP_DEFINE_DANGEROUS_EXPORT_OBJECT(...)
#endif
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (int)(n); ++i)
using namespace std;
int main() {
CPP_DUMP_SET_OPTION(max_line_width, 80);
CPP_DUMP_SET_OPTION(log_label_func, cp::log_label::filename());
CPP_DUMP_SET_OPTION(enable_asterisk, true);
int N;
cin >> N;
vector<int> X(N);
rep(i, N) { cin >> X[i]; }
dump(X);
// 続く...
}
コンパイル時は、-D DEFINED_ONLY_IN_LOCAL
をオプションにつけて実行します。
すると、提出時には、dump()マクロが消えるようになります!!
g++ main.cpp -D DEFINED_ONLY_IN_LOCAL
終わりに
良かったらGitHubスターをくれると嬉しいです!
Discussion