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

2023/09/27に公開

はじめに

Python のprint()や JavaScript のconsole.log()、PHP のvar_dump()。これらはあらゆる型の変数を print できる関数で、デバッグにめちゃくちゃ便利です。 デバッガだと時間がかかるし、簡単にプログラムを止めずに変数の中身のログがとることができるので、僕は良く使っています。

でも、C++にはこんな関数はありません。なので、例えば vector の中身を print したい!と思っても、いちいち for ループを書いて、cout << a[i] << endl;なんてやらなければなりません。 もし Python みたいにprint(変数名1, 変数名2, ...)とするだけで、あらゆる変数の中身を表示することが出来たら…。

そこで、あらゆる型の変数を自動でフォーマットしてプリントするライブラリ cpp-dump を本気で作ってみました! オートインデント機能、色付きの出力、JavaScript、Python、C++ に似た文字列表現、20 種類以上のマニピュレータなど、cpp-dump は変数を手軽に分かりやすくプリントするためにあらゆる機能を備えています。

このライブラリの特徴:

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

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

注意

この記事はcpp-dump の README.mdの日本語版になります!
ただ、記事が長め+ChatGPT を使って翻訳したものがベースになっているので、僕(人間)が書いた記事を読みたい場合や、競プロなどで使いたくて上級者向けの情報が要らないという人は次の記事を読んでください!

https://zenn.dev/sassan/articles/4878e79272ed61

このライブラリの使い方

cpp_dump(expressions...)マクロに変数を渡すだけです!
サンプルコード全体はこちら

std::vector<std::vector<int>> my_vector{{3, 5, 8, 9, 7}, {9, 3, 2, 3, 8}};
cpp_dump(my_vector);

このライブラリの特徴

幅広い型に対応

対応している型は vector だけではありません。map, set, tuple, pair など、ありとあらゆる型に対応しています。
変数の文字列表現は可読性の良い JavaScript、Python、C++のシンタックスに似ています。
サポートしている全ての型を見る
サンプルコード全体はこちら

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::vector<std::string> my_vector(3, "This is a test string.");

cpp_dump(my_vector);
my_vector.push_back("This is a test string.");
cpp_dump(my_vector);

[dump]の代わりにファイル名や行数を表示させることも可能

[dump]の代わりにファイル名や行数を表示させることも可能です。
その場合は以下のコードで設定します。関数名を表示することもできます。詳しくはdumpのカスタマイズ方法をご覧ください。
サンプルコード全体はこちら

// Print the filename and line instead of [dump]
CPP_DUMP_SET_OPTION(log_label_func, cp::log_label::filename());
// Print along with the function name
CPP_DUMP_SET_OPTION(log_label_func, cp::log_label::filename(true));

出力のカラーリングはカスタマイズ可能

以下のコードのようにすることで出力のカラーリングをカスタマイズできます。
サンプルコード全体はこちら

// 色数を増やす
CPP_DUMP_SET_OPTION(es_value.log, "\x1b[02m");                 // log: 灰色
CPP_DUMP_SET_OPTION(es_value.expression, "\x1b[34m");          // expression: 青
CPP_DUMP_SET_OPTION(es_value.reserved, "\x1b[38;5;39m");       // reserved: 明るい青
CPP_DUMP_SET_OPTION(es_value.number, "\x1b[38;5;150m");        // number: 明るい緑
CPP_DUMP_SET_OPTION(es_value.character, "\x1b[38;5;172m");     // character: オレンジ
CPP_DUMP_SET_OPTION(es_value.escaped_char, "\x1b[38;5;220m");  // escaped_char: 明るいオレンジ
CPP_DUMP_SET_OPTION(es_value.op, "\x1b[02m");                  // op: 灰色
CPP_DUMP_SET_OPTION(es_value.identifier, "\x1b[32m");          // identifier: 緑
CPP_DUMP_SET_OPTION(es_value.member, "\x1b[96m");              // member: 明るいシアン
CPP_DUMP_SET_OPTION(es_value.unsupported, "\x1b[31m");         // unsupported: 赤
CPP_DUMP_SET_OPTION(es_value.bracket_by_depth, (std::vector<std::string>{
        "\x1b[33m",  // bracket_by_depth[0]: 黄色
        "\x1b[35m",  // bracket_by_depth[1]: マゼンタ
        "\x1b[36m",  // bracket_by_depth[2]: シアン
}));
CPP_DUMP_SET_OPTION(es_value.class_op, "\x1b[02m");   // class_op: 灰色
CPP_DUMP_SET_OPTION(es_value.member_op, "\x1b[02m");  // member_op: 灰色
CPP_DUMP_SET_OPTION(es_value.number_op, "");          // number_op: デフォルト

// クラスやメンバ、数値の演算子(::, <>, (), -, +, etc...)に
// 色(class_op, member_op, number_op)を付ける
CPP_DUMP_SET_OPTION(detailed_class_es, true);
CPP_DUMP_SET_OPTION(detailed_member_es, true);
CPP_DUMP_SET_OPTION(detailed_number_es, true);

// IDEのシンタックスハイライトのようなカラーリングにする
// CPP_DUMP_SET_OPTION(es_style, cp::types::es_style_t::by_syntax);

Light テーマの場合はこのような出力になります。

色付き出力をオフにするには次のコードを使います。
サンプルコード全体はこちら

// 色付き出力をオフにする
CPP_DUMP_SET_OPTION(es_style, cp::types::es_style_t::no_es);

ユーザー定義型も対応させることが可能

マクロを使うか演算子を定義することで、ユーザー定義型も対応させることが可能です。詳しくはユーザー定義型の print の仕方をご覧ください。
サンプルコード全体はこちら

CPP_DUMP_DEFINE_EXPORT_OBJECT_GENERIC(i, str());

20 種類以上のマニピュレータ

このライブラリには 20 種類以上のマニピュレータがあり、簡単にフォーマットを変更したり、情報を付加したりすることができます。詳しくはマニピュレーターを使ってフォーマットするをご覧ください。

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

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

数値の基数を変更するマニピュレータ

文字列をエスケープするマニピュレータ

必要要件

  • C++17 以上
  • ヘッダーオンリーライブラリなので追加のビルドや依存関係などはありません!

インストール方法

簡単な方法

  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 を使う方法

cmake --installでヘッダーを/usr/local/include/にコピーする

git clone https://github.com/philip82148/cpp-dump
cd cpp-dump
cmake -S . -B build # ヘッダーオンリーライブラリなので設定は必要ない
sudo cmake --install build
# (このあと「cpp-dump」フォルダは削除してよい)
#include <cpp-dump.hpp>

FetchContentを使う方法

CMakeLists.txt
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Generate compile_commands.json (optional)
include(FetchContent)
# Fetch cpp-dump
FetchContent_Declare(cpp-dump
    GIT_REPOSITORY https://github.com/philip82148/cpp-dump
    GIT_TAG main
)
FetchContent_MakeAvailable(cpp-dump)
# Link cpp-dump to your app
target_link_libraries(MyApp PRIVATE cpp-dump)
#include <cpp-dump.hpp>

カスタマイズ方法

カスタマイズする際は、次のようにします。

custom-cpp-dump.hpp
#ifdef DEBUGGING
#include "path/to/cpp-dump/cpp-dump.hpp"
namespace cp = cpp_dump;
// このマクロはソースファイルでもヘッダファイルでも使えますが、
// 同じオプションに対して2回以上使わないように気を付けてください!
CPP_DUMP_SET_OPTION_GLOBAL(max_line_width, 100);

// テンプレートの実体化をきちんと行うために
// サポートしたい型をcpp_dump(...)でprintしている翻訳単位の少なくとも一つにこれらが含まれている必要があります
// 一つの方法はこんな風にヘッダファイルに書いてそのヘッダーファイルを必要な場所で#includeすることです
CPP_DUMP_DEFINE_EXPORT_ENUM(my_enum, my_enum::a, my_enum::b, my_enum::c);
CPP_DUMP_DEFINE_EXPORT_OBJECT(my_class, member1, member2());
CPP_DUMP_DEFINE_EXPORT_OBJECT_GENERIC(member3, member4());
#else
#define cpp_dump(...)
#define CPP_DUMP_SET_OPTION(...)
#endif
main.cpp
#include "path/to/custom-cpp-dump.hpp"

int main() {
  cpp_dump(vec | cp::back());
}

関数の中で設定を変更したい場合はCPP_DUMP_SET_OPTION()を使います。

main.cpp
#include "path/to/custom-cpp-dump.hpp"

void func() {
  CPP_DUMP_SET_OPTION(print_expr, false);
  cpp_dump(vec | cp::back());
  CPP_DUMP_SET_OPTION(print_expr, true);
}

設定可能項目

変数も参照してください。

max_line_width

型: std::size_t デフォルト値: 160

cpp_dump()と、その内部で使われているcpp_dump::export_var()が出力する文字列の最大行幅。

max_depth

型: std::size_t デフォルト値: 4

cpp_dump::export_var()が再帰的に呼び出される最大の回数。

max_iteration_count

型: std::size_t デフォルト値: 16
コンテナなどで、cpp_dump::export_var()をイテレーションする回数。cpp_dump::export_var()の一回の呼び出しでcpp_dump::export_var()は最大(max_iteration_count^(max_depth+1)-1)/(max_iteration_count-1) 回呼び出されることに注意。

cont_indent_style

型: enum class cpp_dump::types::cont_indent_style_t デフォルト値: cpp_dump::types::cont_indent_style_t::when_nested
Container、Set、Map カテゴリの型(対応型一覧参照)のインデントのスタイル。

オプション名 説明
minimal max_line_width を超えない限りインデントしない
when_nested デフォルト。要素/キー/値の型が Container/Set/Map/Tuple カテゴリの型ならば常にインデントする
when_non_tuples_nested 要素/キー/値の型が Container/Set/Map カテゴリの型ならば常にインデントする。Tuple カテゴリの型の場合はインデントしない
always Container/Set/Map がネストされていなくても常にインデントする

enable_asterisk

型: bool デフォルト値: false
cpp_dump::export_var()が Asterisk カテゴリの型(対応型一覧参照)をプリントするかどうか。

型: bool デフォルト値: true
cpp_dump()が式を表示するかどうか。

log_label_func

型: cpp_dump::types::log_label_func_t デフォルト値 cpp_dump::log_label::default_func
cpp_dump()が出力の最初にプリントするラベルを返す関数。

es_style

型: enum class cpp_dump::types::es_style_t デフォルト値cpp_dump::types::es_style_t::original
出力のカラースキーム。

オプション名 説明
original デフォルト値
by_syntax 標準的なシンタックスハイライトと同様の色付けにする。ポインタや bitset、complex などがoriginalとは違う色付けになる
no_es 色付き出力をオフにする

es_value

型: cpp_dump::types::es_value_t デフォルト値: (デフォルトコンストラクタ、参照)
エスケープシーケンスの値。

detailed_class_es

型: bool デフォルト値: false
true にすると、クラス名の演算子 (::<> など) に es_value.class_op カラーが使用される。

detailed_member_es

型: bool デフォルト値: false
true にすると、メンバの演算子 (() など) に es_value.member_op カラーが使用される。

detailed_number_es

型: bool デフォルト値: false
true に設定すると、数値内の演算子 (-+ など) に es_value.number_op カラーが使用される。

詳しい使用方法

マクロ

/**
 * 式とその結果の文字列表現をstd::clogまたは他の設定可能な出力先に出力する。
 * 出力を変更する場合は、cpp_dump::write_log()の明示的特殊化を定義する。
 * このマクロは内部でcpp_dump::export_var()を使用している。
 */
#define cpp_dump(expressions...)

/**
 * cpp_dump::export_var()が型Tをサポートするようにする。
 * 表示するメンバー関数はconstでなければならない。
 */
#define CPP_DUMP_DEFINE_EXPORT_OBJECT(T, members...)

/**
 * cpp_dump::export_var()が指定されたメンバーを持つすべての型をサポートするようにする。
 * 表示するメンバー関数はconstでなければならない。
 * このマクロ内のコンパイルエラー(Ambiguous Function Call)は、SFINAEにより報告されない。
 */
#define CPP_DUMP_DEFINE_EXPORT_OBJECT_GENERIC(members...)

/**
 * cpp_dump::export_var()がenum型Tをサポートするようにする。
 */
#define CPP_DUMP_DEFINE_EXPORT_ENUM(T, members...)

/**
 * cpp_dump::options名前空間内の変数に値を代入する。
 */
#define CPP_DUMP_SET_OPTION(variable, value)

/**
 * cpp_dump::options名前空間内の変数に値を代入する。
 * グローバル空間で実行する、つまりmain()が始まる前に実行したい場合はこれを使用する。
 */
#define CPP_DUMP_SET_OPTION_GLOBAL(variable, value)

namespace cpp_dump::types {

/**
 * cpp_dump::options::cont_indent_styleの型。
 * cpp_dump::export_var()はこの型をサポートする。
 */
enum class cont_indent_style_t { minimal, when_nested, when_non_tuples_nested, always };

/**
 * cpp_dump::options::es_style の型。
 * cpp_dump::export_var() はこの型をサポートする。
 */
enum class es_style_t { no_es, original, by_syntax };

/**
 * cpp_dump::options::es_value の型。
 * cpp_dump::export_var() はこの型をサポートする。
 */
struct es_value_t {
  std::string log = "\x1b[02m";                           // 灰色
  std::string expression = "\x1b[36m";                    // シアン
  std::string reserved{};                                 // デフォルト
  std::string number{};                                   // デフォルト
  std::string character{};                                // デフォルト
  std::string escaped_char = "\x1b[02m";                  // 灰色
  std::string op = "\x1b[02m";                            // 灰色
  std::string identifier = "\x1b[32m";                    // グリーン
  std::string member = "\x1b[36m";                        // シアン
  std::string unsupported = "\x1b[31m";                   // レッド
  std::vector<std::string> bracket_by_depth{"\x1b[02m"};  // 灰色
  std::string class_op = "\x1b[02m";                      // 灰色
  std::string member_op = "\x1b[02m";                     // 灰色
  std::string number_op{};                                // デフォルト
};

using log_label_func_t = std::function<std::string(std::string_view, std::size_t, std::string_view)>;

}  // namespace cpp_dump::types

変数

namespace cpp_dump::options {

/**
 * cpp_dump()およびcpp_dump::export_var()によって返される文字列の最大行幅。
 */
inline std::size_t max_line_width = 160;

/**
 * cpp_dump::export_var()が再帰的に呼び出される最大回数。
 */
inline std::size_t max_depth = 4;

/**
 * cpp_dump::export_var()がイテレータを介して繰り返される最大回数。
 * 一度の呼び出しでcpp_dump::export_var()は最大で
 * (max_iteration_count^(max_depth+1)-1)/(max_iteration_count-1)回再帰的に呼び出されます。
 */
inline std::size_t max_iteration_count = 16;

/**
 * Container, Set, Map カテゴリの型(「対応型一覧」参照)のインデントのスタイル。
 */
inline types::cont_indent_style_t cont_indent_style = types::cont_indent_style_t::when_nested;

/**
 * cpp_dump()がAsteriskカテゴリの型(「対応型一覧」参照)を出力するかどうか。
 */
inline bool enable_asterisk = false;

/**
 * cpp_dump()が式を出力するかどうか。
 */
inline bool print_expr = true;

/**
 * cpp_dump()が出力の先頭に表示するラベルを返す関数。
 */
inline types::log_label_func_t log_label_func = log_label::default_func;

/**
 * エスケープシーケンスのスタイル(出力のカラーリング)。
 */
inline types::es_style_t es_style = types::es_style_t::original;

/**
 * エスケープシーケンスの値(出力のカラーリング)。
 */
inline types::es_value_t es_value;

/**
 * true の場合、クラス名のオペレータ(::, <>, など)に 'es_value.class_op' の色が使用される。
 */
inline bool detailed_class_es = false;

/**
 * true の場合、メンバーのオペレータ((), など)に 'es_value.member_op' の色が使用される。
 */
inline bool detailed_member_es = false;

/**
 * true の場合、数値のオペレータ(-, +, など)に 'es_value.number_op' の色が使用される。
 */
inline bool detailed_number_es = false;

}  // namespace cpp_dump::options

関数

namespace cpp_dump {

/**
 * 変数の文字列表現を返す。
 * cpp_dump()は内部でこの関数を使用する。
 */
template <typename T>
std::string export_var(const T &value);

/**
 * cpp_dump()はこの関数を使用してログを出力する。
 * この関数をカスタマイズするためには、この関数のvoid型の明示的特殊化を定義する。
 */
template <typename = void>
void write_log(std::string_view output) {
  std::clog << output << std::endl;
}

// マニピュレータ(詳細は「マニピュレータを使ってフォーマットする」を参照)
front(std::size_t iteration_count = options::max_iteration_count);
middle(std::size_t iteration_count = options::max_iteration_count);
back(std::size_t iteration_count = options::max_iteration_count);
both_ends(std::size_t half_iteration_count = options::max_iteration_count / 2);
index();
int_style(int base, int digits = -1, int chunk = 0,
    bool space_fill = false, bool make_unsigned_or_no_space_for_minus = false);
bin(int digits = -1, int chunk = 0, bool space_fill = false);
oct(int digits = -1, int chunk = 0, bool space_fill = false);
hex(int digits = -1, int chunk = 0, bool space_fill = false);
dec(int digits = -1, int chunk = 0, bool space_fill = true);
ubin(int digits = -1, int chunk = 0, bool space_fill = false);
uoct(int digits = -1, int chunk = 0, bool space_fill = false);
uhex(int digits = -1, int chunk = 0, bool space_fill = false);
udec(int digits = -1, int chunk = 0, bool space_fill = true);
map_k(return_value_of_manipulator);
map_v(return_value_of_manipulator);
map_kv(return_value_of_manipulator_for_key, return_value_of_manipulator_for_value);
format(const char *f);
bw(bool left = false);
boolnum();
stresc();
charhex();
addr(std::size_t depth = 0);

}  // namespace cpp_dump

// 「[dump]のカスタマイズ方法」を参照
namespace cpp_dump::log_label {

std::string default_func(std::string_view, std::size_t, std::string_view);
types::log_label_func_t line(bool show_func = false, int min_width = 0);
types::log_label_func_t basename(bool show_func = false, int min_width = 0);
types::log_label_func_t filename(bool show_func = false, int min_width = 0);
types::log_label_func_t fullpath(int substr_start, bool show_func = false, int min_width = 0);
types::log_label_func_t fixed_length(int min_width, int max_width,
    int substr_start, bool show_func = false);

}  // namespace cpp_dump::log_label

ユーザー定義型の print の仕方

ユーザー定義型を print する方法は 3 つあります。

方法 1. CPP_DUMP_DEFINE_EXPORT_OBJECT() マクロを使う

これは最も安全・簡単にcpp_dump()をユーザー定義型に対応させる方法です。
サンプルコード全体はこちら

// グローバルスコープからアクセス可能な場所(プライベート、または関数内で定義されていない)に配置
struct class_A {
  int i;
  std::string str() const { return std::to_string(i); }
};

// グローバルスコープ内
// CPP_DUMP_DEFINE_EXPORT_OBJECT(type_name, members...)
CPP_DUMP_DEFINE_EXPORT_OBJECT(class_A, i, str());

// 関数内
class_A my_class_A{10};
cpp_dump(my_class_A);

user-defined-class.png

enum に対しては、CPP_DUMP_DEFINE_EXPORT_ENUM()があります。
サンプルコード全体はこちら

// グローバルスコープからアクセス可能な場所(プライベート、または関数内で定義されていない)に配置
enum class enum_A { a, b, c };

// グローバルスコープ内
// CPP_DUMP_DEFINE_EXPORT_ENUM(enum_name, members...)
CPP_DUMP_DEFINE_EXPORT_ENUM(enum_A, enum_A::a, enum_A::b, enum_A::c);

// 関数内
enum_A my_enum_A = enum_A::c;
cpp_dump(my_enum_A);

user-defined-enum.png

方法 2. CPP_DUMP_DEFINE_EXPORT_OBJECT_GENERIC() マクロを使う

このマクロを使うと指定されたメンバーを持つすべての型をcpp_dump()が出力できるようになります。
このマクロは、ユーザー型がグローバルスコープからアクセス可能であることやユーザー型の型名を必要としません。

このマクロを 2 回以上使用する場合は、Ambiguous function call のコンパイルエラーに注意してください。
なお、そのようなエラーが発生しても、SFINAE により報告されず、ユーザー型は print できないままです。
サンプルコード全体はこちら

// グローバルスコープ内
// CPP_DUMP_DEFINE_EXPORT_OBJECT_GENERIC(members...)
CPP_DUMP_DEFINE_EXPORT_OBJECT_GENERIC(i, str());

// どこでもよい
struct class_A {
  int i;
  std::string str() const { return std::to_string(i); }
};

// 関数内
class_A my_class_A{10};
cpp_dump(my_class_A);

user-defined-class2.png

3. std::ostream& operator<<(std::ostream&, const T &) 演算子を定義する

std::ostream& operator<<(std::ostream&, const T &) 演算子を定義することでもユーザー定義型をサポートすることができます。
サンプルコード全体はこちら

// グローバルスコープからアクセス可能な場所(プライベート、または関数内で定義されていない)に配置
struct class_A {
  int i;
  std::string str() const { return std::to_string(i); }
};

// グローバルスコープ
std::ostream &operator<<(std::ostream &os, const class_A &a) {
  os << "class_A{ i= " << a.i << ", str()= \"" << a.str() << "\" }";
  return os;
}

// 関数内
class_A my_class_A{10};
cpp_dump(my_class_A);

[dump]のカスタマイズ方法

cpp_dump::options::log_label_func に関数を代入することでカスタマイズできます。
ライブラリ側でcpp_dump::options::log_label_func に代入する関数を作成する関数が用意されているので、自分で関数を作成する必要はありません。

namespace cpp_dump::types {

using log_label_func_t =
  std::function<std::string(std::string_view fullpath, std::size_t line, std::string_view func_name)>;

}  // namespace cpp_dump::types

namespace cpp_dump::log_label {

// cpp_dump::options::log_label_func に代入されているデフォルトの関数。
std::string default_func(std::string_view, std::size_t, std::string_view) {
  return "[dump] ";
}

// cpp_dump::options::log_label_func に割り当て可能な関数を作成する関数群。
types::log_label_func_t line(bool show_func = false, int min_width = 0);
types::log_label_func_t basename(bool show_func = false, int min_width = 0);
types::log_label_func_t filename(bool show_func = false, int min_width = 0);
types::log_label_func_t fullpath(int substr_start, bool show_func = false, int min_width = 0);
types::log_label_func_t fixed_length(int min_width, int max_width,
    int substr_start, bool show_func = false);

}  // namespace cpp_dump::log_label

namespace cpp_dump::options {

inline types::log_label_func_t log_label_func = log_label::default_func;

}  // namespace cpp_dump::options

マニピュレーターを使ってフォーマットする

マニピュレーターを使うことで、簡単に出力のフォーマットを変更できます。
たとえば、配列 や map、set のどの、いくつの要素を表示するかを、frontmiddlebackboth_ends マニピュレーターで選べます。
サンプルコード全体はこちら

// 1次元目の最後の10要素、2次元目の最初の5要素と最後の5要素を表示します。
cpp_dump(some_huge_vector | cp::back(10) | cp::both_ends(5) | cp::dec(2));

manipulator-front-etc.png

また、index マニピュレーターで、配列のインデックスを表示できます。
サンプルコード全体はこちら

CPP_DUMP_SET_OPTION(max_iteration_count, 5);

// ベクターのインデックスを表示します。
cpp_dump(some_huge_vector | cp::dec(2) | cp::index());

manipulator-index.png

さらに、整数のフォーマットを変更するマニピュレーターなど、他にも多くのマニピュレーターがあります。
サンプルコード全体はこちら

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

manipulator-int-style.png

マニピュレータの使用方法

マニピュレーターは'|' 演算子か'<<' 演算子で使用できます。
マニピュレーターの適用順序が表示に影響するものとそうでないものがあります。

cpp_dump(variable | manipulatorA() | manipulatorB());
cpp_dump(manipulatorA() << manipulatorB() << variable);

front()middle()back()both_ends() マニピュレータ

namespace cpp_dump {

front(std::size_t iteration_count = options::max_iteration_count);
middle(std::size_t iteration_count = options::max_iteration_count);
back(std::size_t iteration_count = options::max_iteration_count);
both_ends(std::size_t half_iteration_count = options::max_iteration_count / 2);

}  // namespace cpp_dump

これらのマニピュレーターは適用順序が表示に影響します。

左側のマニピュレーターから順に配列/Map/Set のより外側の次元に作用します。
注意:
front() 以外のこれらのマニピュレーターはコンテナのサイズを計算します。std::size() でサイズを計算できないコンテナは O(N) の計算コストがかかります。特に、無限長のシーケンスをこれらのマニピュレーターに渡すと無限ループが発生します。
サンプルコード全体はこちら

// 1次元目の最後の10要素、2次元目の最初の5要素と最後の5要素を表示します。
cpp_dump(some_huge_vector | cp::back(10) | cp::both_ends(5) | cp::dec(2));

manipulator-front-etc.png

index() マニピュレータ

cpp_dump::index();

front()等のマニピュレーターとは異なり、index() マニピュレーターは変数内のすべてのシーケンスコンテナに作用します。(順序は関係ありません。)
このマニピュレータは Map/Set には影響しません。
サンプルコード全体はこちら

cpp_dump(some_huge_vector | cp::dec(2) | cp::index());

manipulator-index.png

int_style() マニピュレータ

namespace cpp_dump {

int_style(int base, int digits = -1, int chunk = 0,
    bool space_fill = false, bool make_unsigned_or_no_space_for_minus = false);

bin(int digits = -1, int chunk = 0, bool space_fill = false) {
  return int_style(2, digits, chunk, space_fill, false);
}
oct(int digits = -1, int chunk = 0, bool space_fill = false) {
  return int_style(8, digits, chunk, space_fill, false);
}
hex(int digits = -1, int chunk = 0, bool space_fill = false) {
  return int_style(16, digits, chunk, space_fill, false);
}
dec(int digits = -1, int chunk = 0, bool space_fill = true) {
  return int_style(10, digits, chunk, space_fill, false);
}

ubin(int digits = -1, int chunk = 0, bool space_fill = false) {
  return int_style(2, digits, chunk, space_fill, true);
}
uoct(int digits = -1, int chunk = 0, bool space_fill = false) {
  return int_style(8, digits, chunk, space_fill, true);
}
uhex(int digits = -1, int chunk = 0, bool space_fill = false) {
  return int_style(16, digits, chunk, space_fill, true);
}
udec(int digits = -1, int chunk = 0, bool space_fill = true) {
  return int_style(10, digits, chunk, space_fill, true);
}

}  // namespace cpp_dump

int_style()base パラメーターは数値の基数を決めるもので 281016 の値をサポートしています。他の値では、このマニピュレーターは何も行いません。
digits パラメーターは digits >= 0 と digits <= '最大桁数' の値をサポートします。ここで '最大桁数' は、指定された base に対してその整数型が表現できる最大桁数です。他の値がセットされた場合は digits = '最大桁数' として扱われます。
chunk パラメーターは chunk >= 0 の値をサポートします。他の値は chunk = 0 として扱われます。

index() マニピュレーターと同様に、int_style() マニピュレーターは変数内のすべての整数に作用します。(順序は関係ありません。)
bin(...)oct(...)hex(...)ubin(...)uoct(...)uhex(...)dec(...)udec(...)int_style(...) のエイリアスです。

bin()oct()hex()dec() マニピュレーターは符号付き整数型に対しては正の値には余分に 1 スペース追加し、負の値には-記号を追加します。
符号なし整数型に対しては、これらのマニピュレーターは余分なスペースや-記号を追加しません。
サンプルコード全体はこちら

cpp_dump(signed_int_vector | cp::front(2) | cp::hex(2));
cpp_dump(unsigned_int_vector | cp::front(2) | cp::hex(2));
cpp_dump(signed_int_vector | cp::front(2) | cp::dec(2));
cpp_dump(unsigned_int_vector | cp::front(2) | cp::dec(2));

manipulator-bin-etc.png

ubin()uoct()uhex() マニピュレーターは、すべての整数型を符号なし整数として解釈します。元の型が符号なしでない場合、サフィックスuが表示されます。
ただし、udec() マニピュレーターはこれらとは違います。udec()マニピュレーターは符号付き型を符号付き型として解釈しますが、正の値にはマイナスのためのスペースを追加しません。これは、負の値を持たない符号付き整数のコンテナを表示するのに適しています。
サンプルコード全体はこちら

cpp_dump(signed_int_vector | cp::front(2) | cp::uhex());
cpp_dump(unsigned_int_vector | cp::front(2) | cp::uhex(2));
cpp_dump(signed_int_vector | cp::front(2) | cp::udec(2));
cpp_dump(unsigned_int_vector | cp::front(2) | cp::udec(2));

manipulator-ubin-etc.png

format() マニピュレータ

cpp_dump::format(const char *f);

このマニピュレーターは snprintf() を使用して数値(整数および浮動小数)をフォーマットします。フォーマット指定子で指定された型が実際の型と一致していることを確認してください。
サンプルコード全体はこちら

cpp_dump(pi | cp::format("%.10f"));

manipulator-format.png

bw()boolnum() マニピュレータ

cpp_dump::bw(bool left = false);
cpp_dump::boolnum();

これらのマニピュレーターは bool 型のフォーマットに使用します。
bw() マニピュレーターは、bool 値が true の場合に false の幅に合わせてスペースを追加します。bw は「bool width」の略です。
boolnum() マニピュレーターは、bool 値を 1 または 0 として表示します。
サンプルコード全体はこちら

cpp_dump(bool_vector | cp::bw());
cpp_dump(bool_vector | cp::bw(true));
cpp_dump(bool_vector | cp::boolnum());

manipulator-bw-boolnum.png

stresc() マニピュレータ

cpp_dump::stresc();

このマニピュレーターは文字列をエスケープします。エスケープされた文字には es_value.escaped_char カラーが使用されます。
サンプルコード全体はこちら

cpp_dump("\a\t\\\"\n\x7f need to be escaped.");
cpp_dump("\a\t\\\"\n\x7f need to be escaped." | cp::stresc());

manipulator-stresc.png

charhex() マニピュレータ

cpp_dump::charhex();

このマニピュレーターは char をその 16 進数と一緒に表示します。このマニピュレータを使った時、文字列表現の幅は固定されます。
サンプルコード全体はこちら

for (auto c : "\a\t\\\"\n\x7f ABC") cpp_dump(c | cp::charhex());

manipulator-charhex.png

addr() マニピュレータ

cpp_dump::addr(std::size_t depth = 0);

このマニピュレーターはポインタのアドレスを表示します。
depth パラメーターを使用して、アドレスを表示するポインタの深さを指定できます。
サンプルコード全体はこちら

int my_int = 15;
int *int_ptr = &my_int;
int **int_ptr_ptr = &int_ptr;

cpp_dump(int_ptr_ptr);
cpp_dump(int_ptr_ptr | cp::addr());
cpp_dump(int_ptr_ptr | cp::addr(1));

manipulator-addr.png

map_*() マニピュレータ

namespace cpp_dump {

map_k(マニピュレータの戻り値);
map_v(マニピュレータの戻り値);
map_kv(キーのマニピュレータの戻り値, バリューのマニピュレータの戻り値);

}  // namespace cpp_dump

これらのマニピュレーターは適用順序が表示に影響します。

これらのマニピュレーターは (multi)map に作用します。
以下の例では、キーが 16 進数で表示され、値がコンテナの場合は値の前部が省略されます。

cpp_dump(cp::front() << cp::map_kv(cp::hex(), cp::back()) << map);
cpp_dump(map | cp::front() | cp::map_kv(cp::hex(), cp::back()));

出力先を標準エラー出力から変更する

出力先を変更するには、cpp_dump::write_log()void での明示的特殊化を定義します。

// ヘッダーファイルにこれを書いてもかまいません。
// ソースファイルに書く場合は、inline キーワードを削除できます。
template <>
inline void cpp_dump::write_log(std::string_view output) {
  elsewhere << output << std::endl;
}

複雑な式の cpp_dump(...)への渡し方

コンマを含む式

コンマを含む式は(...)でくくってください。

cpp_dump(std::is_same_v<T, U>); // コンパイルエラー!
cpp_dump((std::is_same_v<T, U>)); // 正しい

可変長引数テンプレートの引数

可変長引数テンプレートの引数を渡す際は、他に引数を渡さないでください。

template <typename... Args>
void variadic_template_func(Args &&...args) {
  int i = 0;
  cpp_dump(args..., i); // Compile error!
  cpp_dump(args...), cpp_dump(i); // Correct
}

競技プログラミングでの便利な使い方

競プロではcpp_dump()と打つのは長いので、マクロでdump()と短くしてしまいましょう。

#define dump(...) cpp_dump(__VA_ARGS__)

また、提出時にはダンプ関連のコードは消えるようにしておくと便利です。
具体的には、以下のようにします。

#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

または

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

対応型一覧

カテゴリ 型 T がサポートされる条件
Arithmetic std::is_arithmetic_v<T> == true bool, char, int, long, float, double
String T が std::string_view に変換可能 std::string, const char *, std::string_view
Container T が範囲 for 文に対応 std::vector, std::forward_list, C スタイルの配列
Map T が std::map, std::unordered_map, std::multimap, または std::unordered_multimap のいずれかである
Set T が std::set, std::unordered_set, std::multiset, または std::unordered_multiset のいずれかである
Tuple T が std::tuple_size_v<T> に対応 std::tuple, std::pair, ユーザー定義タプル
FIFO/LIFO T が std::queue, std::priority_queue, または std::stack のいずれかである
Pointer T がポインタまたはスマートポインタである int *, std::shared_ptr, std::unique_ptr
Reference T が std::reference_wrapper である
Exception T が std::exception に変換可能
Other T が std::bitset, std::complex, std::optional, std::variant, std::type_info, std::type_index, または std::source_location(C++20 以降、g++および MSVC のみ)
User-defined CPP_DUMP_DEFINE_EXPORT_OBJECT(T, members...); がグローバルスコープにあり、表示されるメンバー関数が const である
Enum CPP_DUMP_DEFINE_EXPORT_ENUM(T, members...); がグローバルスコープにある
Ostream 上記の条件がすべて満たされず、std::is_function_v<T> == false && std::is_member_pointer_v<T> == false であり、関数 std::ostream& operator<<(std::ostream&, const T &) が定義されている。T の文字列表現が空文字列でないこと(これによりマニピュレータはサポートされなくなる)。
User-defined 2 上記の条件がすべて満たされず、T がグローバルスコープのCPP_DUMP_DEFINE_EXPORT_OBJECT_GENERIC(members...); で指定されたすべてのメンバーを持ち、表示されるメンバー関数が const である。
Asterisk 上記の条件がすべて満たされず、cpp_dump::options::enable_asterisk == true であり、関数 TypeExceptT operator*(const T &) または const メンバー関数 TypeExceptT T::operator*() const が定義されている。 イテレータ

表示例

幅広い型に対応の画像も参照してください。

# Arithmetic
true, 'c', 1, 3.140000

# String
"A normal string"
`A string with '"' or newline(s)`

# Container
[ value1, value2, ... ]

# Map
{ key1: value1, key2: value2, ... },
{ key1 (multiplicity1): [ value1a, value1b, ... ], key2 (multiplicity2): [ ... ], ... }

# Set
{ value1, value2, ... },
{ value1 (multiplicity1), value2 (multiplicity2), ... }

# Tuple
( value1, value2, ... )

# FIFO/LIFO
std::queue{ front()= value, back()= value, size()= integer }

# Pointer
*value
nullptr
0x7fff2246c4d8
# (The address will be displayed when the pointer type is void *
#  or the type the pointer points to is not supported.)

# Reference
true, 'c', 1, 3.140000
# (No change)

# Exception
std::logic_error{ what()= "Error Message" }

# Asterisk
*value

他のカテゴリは、以下のセクションの画像を参考にしてください。

Other -> 幅広い型に対応
User-defined, Enum -> ユーザー定義型の print の仕方

終わりに

良かったら GitHub スターをくれると嬉しいです!

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

GitHubで編集を提案

Discussion