【GitHubスター100個突破】Pythonのprint()のC++版を本気で作ってみた

2023/10/24に公開

はじめに

Python のprint()は数値でも文字列でも配列でも、どのような型の変数でも文字列にしてくれてすごく便利です。

僕は競プロ(競技プログラミング)をやっているのですが、時間との闘いである競プロでは、このようなあらゆる変数表示が出来る関数はデバッグにとても重宝します。

しかし、C++にはそのような関数はありません。特に C++は高速で競プロに向いている言語なので、C++にそのような関数がないのはとてももったいないです。

だったら作ればいいじゃない、ということで、print()の C++バージョンを本気で作ってみました!

今回作ったprint()の C++バージョンの特徴はこんな感じです。

  • 多次元配列や多次元の(多重)マップ、(多重)セット、タプル、複素数やエラーオブジェクトまで、どんな型の変数でも、文字列表現にして標準エラー出力(std::clog)に出力する。
  • 行幅が設定でき、自動でインデントを行う。
  • 変数の文字列表現は JavaScript や Python, C++のシンタックスに似ていて、情報過多で見づらくなることもない、読みやすい出力。 (マニピュレータで情報を付加することもできる)
  • 出力は色付きで、カスタマイズ可能。
  • 20 種類以上のマニピュレータ付きで簡単に出力のフォーマットを変えられる。
  • マクロを使うか演算子を定義するとユーザー定義型もプリントできる。
  • ヘッダーオンリーライブラリで、ビルドなどは不要。

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

注意

このライブラリの READMEの日本語版を以下の記事にしました。

https://zenn.dev/sassan/articles/19db660e4da0a4

でも、上の記事(以下「README 日本語版」と呼びます)は長ったらしいので、詳しい情報が要らない方はまずはこの記事を読みましょう!
この記事でも詳しい情報が知りたい方向けに「README 日本語版」へのリンクを貼ってあります!

まずはテストコード!

説明はいいから試したい!っていう人はこのセクションを読んでください。
詳しいインストール方法・このライブラリの特徴が知りたい人はこのセクションは飛ばして次のセクションから読んでくださいね。

次のコマンドをターミナルで実行して、、
(git が使えない人は次のセクションを参照してください)

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

以下をコピペしてコンパイル&実行します。

#include <bitset>
#include <complex>
#include <iostream>
#include <map>
#include <optional>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <tuple>
#include <utility>
#include <variant>
#include <vector>

#include "./cpp-dump/cpp-dump.hpp"

int main() {
  bool my_bool = true;
  double my_double = 3.141592;
  int my_int = 65;
  char my_char = 'a', LF_char = '\n';
  std::string my_string = "This is a string.";
  int *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{"This is a string."};
  std::vector<std::pair<int, std::string>> vector_of_pairs{{1, "apple"}, {3, "banana"}};

  std::clog << "\n// Basic Type" << std::endl;
  cpp_dump(my_bool, my_double, my_int), cpp_dump(my_string, my_char, LF_char);
  cpp_dump(int_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);

  std::clog << std::endl;
}

すると、このように print されます!

詳しいインストール方法

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

git が使える人は

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

git が使えない人は
Releasesからソースフォルダをダウンロードして、好きな場所に解凍しましょう。
※こちらの方法の場合、最新の機能が一部使えない場合があります。

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

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

他にも CMake を使ったインストール方法などもあります。詳しくはREADME 日本語版の「インストール方法」の項を見てください!

使い方!

cpp_dump(expr...)マクロに変数を渡すだけです!

// In main() or some function
std::vector<std::vector<int>> my_vector{{3, 5, 8, 9, 7}, {9, 3, 2, 3, 8}};
cpp_dump(my_vector);

このライブラリの特徴 8 つ(以上)

このライブラリの特徴について説明します。

1. Python ライクな文字列表現

本物のprint()のようにPython っぽい文字列表現で出力します。情報が多すぎて読みづらくなることもない、見やすい文字列表現です。

std::vector、std::array、配列などのループ可能なオブジェクトの場合

std::map、std::unordered_map の場合

std::tuple または std::pair の場合

std::set、std::unordered_set の場合

2. 内容量が多いと自動的にインデント

内容量が多くて行幅を超えそうな場合は自動でインデントされます!
これ、もとの Python のprint()にはない機能です!

std::vector、std::array などのループ可能なオブジェクトや std::tuple や std::set など

std::map、std::unordered_map の場合

ちなみに多次元配列などは最初から改行されます(設定により変更可能)。
行幅の設定は次のコードで行えます。

CPP_DUMP_SET_OPTION(max_line_width, 行幅の文字数);
// main()の外で設定する場合は
// CPP_DUMP_SET_OPTION_GLOBAL(max_line_width, 行幅の文字数);

デフォルトでは 160 が設定されています。実際の設定例は競プロでの便利な使い方か、README 日本語版の「カスタマイズ方法」の項をご覧ください!

3. [dump]の代わりにファイル名と行数も print 可能

次のコードを使うことで、[dump]の代わりにファイル名と行数もプリント可能です!
詳しくはREADME 日本語版の「[dump]のカスタマイズ方法」の項をご覧ください!

CPP_DUMP_SET_OPTION(log_label_func, cpp_dump::log_label::filename());
// main()の外で設定する場合は
// CPP_DUMP_SET_OPTION_GLOBAL(log_label_func, cpp_dump::log_label::filename());

4. 再帰的に出力するので、多次元配列やネストされた型も出力可能

そのほか、std::multimap や std:queue、std::complex などありとあらゆる型に対応しています!
対応している型についてはREADME 日本語版の「対応型一覧」の項をご覧ください!

5. ユーザー型も print 可能!

マクロを使ったり、オペレータを定義したりすることでユーザー型も print 可能です!
詳しくはREADME 日本語版の「ユーザー定義型の print の仕方」の項をご覧ください!

CPP_DUMP_DEFINE_EXPORT_OBJECT_GENERIC(i, str());

supports-various-types.png

6. 出力の色はカスタマイズ可能

出力の色はカスタマイズすることができます!
下のような IDE のシンタックスハイライトのような色付けも可能です!
詳しくはREADME 日本語版の「出力のカラーリングはカスタマイズ可能」の項をご覧ください。

7. 出力のフォーマットを変える 20 以上のマニピュレータ!

数値の基数や vector の表示する場所を変えるために、20 以上のマニピュレータがあります。
競プロでは僕はよくfront(), back(), both_ends(), dec(),hex(),bin(), index()マニピュレータを使います。
例えばこんな感じです。

namespace cp = cpp_dump;
// めっちゃでっかいvector
vector<vector<int>> some_huge_vector(100, vector<int>(100));
for (int i = 0; i < 100; ++i) {
  for (int j = 0; j < 100; ++j) {
    some_huge_vector[i][j] = ((i + 1) * 7 % 19 - 9) * ((j + 1) * 3 % 19 - 9);
  }
}

// 1次元目の最後の10要素、2次元目の最初の5要素と最後の5要素を表示する
// cp::dec(2)は数値を2文字幅で表示するためのもの
cpp_dump(some_huge_vector | cp::back(10) | cp::both_ends(5) | cp::dec(2));

// vectorのインデックスを表示する
cpp_dump(some_huge_vector | cp::dec(2) | cp::index());

// 2進数、最小16桁表示、4文字ごとに区切る
cpp_dump(0x3e8u | cp::bin(16, 4));
// 8進数、最小6桁表示、3文字ごとに区切る
cpp_dump(0x3e8u | cp::oct(6, 3));
// 16進数、最小4桁表示、2文字ごとに区切る
cpp_dump(0x3e8u | cp::hex(4, 2));
// 最小4桁表示
cpp_dump(0x3e8u | cp::dec(4));

詳しくはREADME 日本語版の「マニピュレーターを使ってフォーマットする」の項をご覧ください!

8~. その他にもカスタマイズ可能な機能や便利な機能あり

その他、カスタマイズ可能な機能がいくつもあります!
詳しくはREADME 日本語版の設定可能項目の項をご覧ください!

競プロでの便利な使い方

競プロではcpp_dump()は長いので、マクロでdump()と短くしてしまいましょう。
また、提出時にはdump()が消えるように以下のようにします。

#ifdef DEFINED_ONLY_IN_LOCAL
#include "./cpp-dump/cpp-dump.hpp"
// <次のセクションの内容はここに追加する>
#define dump(...) cpp_dump(__VA_ARGS__)
namespace cp = cpp_dump;
CPP_DUMP_SET_OPTION_GLOBAL(max_line_width, 80);
CPP_DUMP_SET_OPTION_GLOBAL(log_label_func, cp::log_label::filename());
CPP_DUMP_SET_OPTION_GLOBAL(enable_asterisk, true);
#else
#define dump(...)
#define CPP_DUMP_SET_OPTION(...)
#define CPP_DUMP_SET_OPTION_GLOBAL(...)
#define CPP_DUMP_DEFINE_EXPORT_OBJECT(...)
#define CPP_DUMP_DEFINE_EXPORT_ENUM(...)
#define CPP_DUMP_DEFINE_EXPORT_OBJECT_GENERIC(...)
#endif

#include <bits/stdc++.h>

// AtCoder Libraryを使っている人
// #include <atcoder/all>

#define rep(i, n) for (int i = 0; i < (int)(n); ++i)

using namespace std;

int main() {
  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

or

clang++ ./main.cpp -D DEFINED_ONLY_IN_LOCAL

おまけ: AtCoder ライブラリへの対応のさせ方

AtCoder ライブラリを使っている人は<atcoder/modint>に対応させるために、上のコードの<次のセクションの内容はここに追加する>の部分に以下を追加します!

#include <atcoder/modint>

namespace cpp_dump::_detail {

template <int m>
inline std::string export_var(
    const atcoder::static_modint<m> &mint, const std::string &indent, std::size_t last_line_length,
    std::size_t current_depth, bool fail_on_newline, const export_command &command
) {
  return export_var(mint.val(), indent, last_line_length, current_depth, fail_on_newline, command);
}

template <int m>
inline std::string export_var(
    const atcoder::dynamic_modint<m> &mint, const std::string &indent, std::size_t last_line_length,
    std::size_t current_depth, bool fail_on_newline, const export_command &command
) {
  return export_var(mint.val(), indent, last_line_length, current_depth, fail_on_newline, command);
}

}  // namespace cpp_dump::_detail

終わりに

良ければ GitHub スターをくれると嬉しいです!

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

関連記事

https://zenn.dev/sassan/articles/19db660e4da0a4

GitHubで編集を提案

Discussion