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

2023/10/24に公開

はじめに

※2023/10/24:記事をQiitaからZennに移しました。

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

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

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

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

まずはテストコード!

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

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

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

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

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

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

#include "./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);

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

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

詳しいインストール方法

  1. リポジトリを好きな場所にダウンロードします。
    下記をターミナルで実行するか、Releasesからソースフォルダ(Source codeと書かれたもの)をダウンロードして好きな場所に解凍してください。
git clone https://github.com/philip82148/cpp-dump
  1. ソースファイルでcpp-dump/dump.hppをインクルードすると、cpp_dump::dump()cpp_dump()が使えるようになります!
#include "path/to/cpp-dump/dump.hpp"

※パスはダウンロードした場所に合わせて変えてください。

使い方!

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);

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

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

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, 行幅の文字数);

デフォルトでは160が設定されています。競プロで提出時に無効化できるようにマクロを作りましたが、普通に代入操作で設定することも可能です。詳しくはリポジトリのREADMEを参照してください。

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

次のコードを使うことで、[dump]の代わりにファイル名と行数もプリント可能です!
詳しくはリポジトリのREADMEをご覧ください!

cpp_dump::log_label_func = cpp_dump::log_label::filename();

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

そのほか、std::multimapやstd:queue、std::complexなどありとあらゆる型に対応しています!
対応している型についてはリポジトリのREADMEをご覧ください!

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

マクロを使ったり、オペレータを定義したりすることでユーザー型もprint可能です!
詳しくはリポジトリのREADMEをご覧ください!

CPP_DUMP_DEFINE_DANGEROUS_EXPORT_OBJECT(i, str());

supports-various-types.png

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

出力の色はカスタマイズすることができます!
写真は一例です。
詳しくはリポジトリのREADMEをご覧ください。

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

その他、表示を変えるマニピュレータなど便利な機能があります!
詳しくはリポジトリのREADMEをご覧ください!

配列の一部を省略するマニピュレータ

整数の基底を変えるマニピュレータ

配列のインデックスを表示するマニピュレータ

競プロでの便利な使い方

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

#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);

  // 続く...
}

そして、ローカルでコンパイルするときは以下のようにすることで、ローカルでだけdump()マクロが動くようになります!

g++ ./main.cpp -D DEFINED_ONLY_IN_LOCAL

or

clang++ ./main.cpp -D DEFINED_ONLY_IN_LOCAL

終わりに

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

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

関連記事

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

Discussion