【C++】あらゆる変数をprintするdump関数を本気で作ってみた

2023/09/27に公開

はじめに

最近競プロでC++を使い始めたのですが、C++にはあらゆる型の変数がprintできるようなPythonのprint()のような関数がありません。
なので、例えばデバッグのためにvectorの中身をprintしたい!と思っても、いちいちforループを書いて、cout << a[i] << endl;なんてやらなければなりません

競プロは時間との戦いですから、このようにタイプ数が多いのは好ましくありませんし、同じ理由で時間のかかるデバッガーもあまり使いたくありません。
もしPythonみたいにprint(変数名1, 変数名2, ...)とするだけで、あらゆる変数の中身を表示することが出来たら…。

そこで、あらゆる型に対応したC++のダンプ関数を本気で作ってみました!
今回作ったダンプ関数の特徴はこんな感じです。

  • 多次元配列や多次元の(多重)マップ、(多重)セット、タプル、複素数やエラーオブジェクトまで、どんな型の変数でも、文字列表現にして標準エラー出力(std::clog)に出力する。
  • 行幅が設定でき、自動でインデントを行う。
  • 出力は色付きで、カスタマイズ可能。
  • ヘッダーオンリーライブラリで、ビルドなどは不要。
  • マクロを使うか演算子を定義するとユーザー定義型もダンプできる。
  • 変数の文字列表現はJavaScriptとPython, C++のシンタックスに似ている。

https://github.com/philip82148/cpp-dump

使い方

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をご覧ください!

インストール方法

  1. リポジトリを好きな場所にダウンロードします。

gitが使える人は

git clone https://github.com/philip82148/cpp-dump

もしくは

git submodule add https://github.com/philip82148/cpp-dump

gitが使えない人は
Releasesからソースフォルダをダウンロードして、好きな場所に解凍しましょう。

  1. ソースファイル内で、cpp-dump/dump.hppをincludeします。
#include "path/to/cpp-dump/dump.hpp"

ダウンロードしたフォルダ内にあるdump.hppというファイルをソースフォルダ内でincludeします。
※パスは自身の環境に合わせて変えてください。

  1. あとはソースファイル内で関数を呼び出すだけ!
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スターをくれると嬉しいです!

https://github.com/philip82148/cpp-dump

Discussion