https://blog.naskya.net/

[[ 🗃 ^wzWnj blog ]] :: [📥 Inbox] [📤 Outbox] [💥 Errbox] [🐤 Followers] [🤝 Collaborators] [🏗 Projects] [🛠 Commits]

Clone

HTTPS: git clone https://code.naskya.net/repos/wzWnj

SSH: git clone USERNAME@code.naskya.net:wzWnj

Branches

Tags

main :: content / post /

meu0vkh5cpl1.md

目的

競技プログラミングのコンテストでは、解答の提出が数分遅れるだけで順位が大きく下がることがあります。そこで、デバッグを素早く行うためのマクロを作っておきます。具体的には、手元の環境だけで実行されるため提出時にいちいちコメントアウトを行う必要が無く

コード
int a = 1, b = 2, c = 3;
std::set<int> A {10, 20, 30};
debug(a, b, c, A);

std::vector<std::vector<int>> B {{0, 1}, {2, 3}, {4, 5}};
debug(B);
出力
a: 1 | b: 2 | c: 3
A: [ 10 20 30 ]
B: [ 0 1 ]
   [ 2 3 ]
   [ 4 5 ]

のように任意の個数の引数を取れて、数値や文字列だけでなく std::{pair, tuple, bitset, array, vector, deque, forward_list, list, initializer_list, stack, queue, priority_queue, set, multiset, map, multimap, unordered_set, unordered_multiset, unordered_map, unordered_multimap, valarray} なども出力でき、2 次元の配列もいい感じに出力してくれるものを作ります。

作ったものはここに置いてあります。

以下ではこの debug マクロがどのように実現されているかについて長々と書いていますが、このマクロをただ使いたいだけの方は使い方という節まで飛ばしてください。

デバッグマクロを作る

まず、1 つの変数を受け取って 変数名: 中身 を出力するだけのマクロを作ってみましょう。

コード
#include <iostream>

#define debug(arg) std::cerr << #arg << ": " << (arg) << '\n'

int main() {
  int a = 1;
  debug(a);
}
出力
a: 1

#変数 で変数の名前{{< note より正確には、(コメントを除く)コードの内容そのもの。例えば debug((a) +b+c) は std::cerr << "(a) +b+c" << ((a) +b+c) << "\n" に置換されます。 >}}を取得できるのがポイントです。

このマクロは取れる引数の型が限られています(例えば std::vector の出力などができない)。そこで出力の部分を関数化し、その関数をオーバーロードすることにします。

コード
#include <algorithm>
#include <iostream>
#include <string_view>
#include <vector>

#define debug(arg) print(#arg, arg)

// 数値や文字列の出力
template <class Tp> void print(std::string_view name, Tp arg) {
  std::cerr << name << ": " << arg << '\n';
}
// std::vector の出力
template <class Tp> void print(std::string_view name, std::vector<Tp> arg) {
  std::cerr << name << ": [ ";
  std::copy(std::cbegin(arg), std::cend(arg), std::ostream_iterator<Tp>(std::cerr, " "));
  std::cerr << "]\n";
}

int main() {
  int a = 1;
  std::vector<int> V {10, 20, 30};

  debug(a);
  debug(V);
}
出力
a: 1
V: [ 10 20 30 ]

main 関数は新しく追加した機能を試すように書き換えています。std::vector<Tp>const std::vector<Tp> & にしたいとか、そういうのはお好きにどうぞ{{< note 最後に載せてある完成品(?)ではちゃんとやっているのでご安心ください>}}。

ところで、標準ライブラリには std::vector 以外にも様々なコンテナがあります。それぞれに対して関数の定義を書いても良いですが、同じようなコードを大量に書くことになるためテンプレートテンプレートパラメータ{{< note 誤記ではない>}}を使って template <class Tp> void print(std::string_view name, std::vector<Tp> arg) の部分を抽象化してみます。

コード
// 前略

#define debug(arg) print(#arg, arg)

template <class Tp> void print(std::string_view name, Tp arg) {
  std::cerr << name << ": " << arg << '\n';
}

template <template <class...> class Container, class... Ts>
void print(std::string_view name, Container<Ts...> arg) {
  std::cerr << name << ": [ ";
  std::copy(std::cbegin(arg), std::cend(arg),
            std::ostream_iterator<typename Container<Ts...>::value_type>(std::cerr, " "));
  std::cerr << "]\n";
}

int main() {
  std::vector<int> A {1, 2, 3};
  std::set<int>    B {2, 4, 6};
  std::deque<char> C {'a', 'x', 'y'};

  debug(A);
  debug(B);
  debug(C);
}
出力
A: [ 1 2 3 ]
B: [ 2 4 6 ]
C: [ a x y ]

これで、様々なコンテナ {{< note std::{vector, deque, list, forward_list, initializer_list, set, multiset, unordered_set, unordered_multiset, map, multimap, unordered_map, unordered_multimap, valarray} >}} の中身を見ることができるようになりました。

ここで「取れるコンテナの要素の型が限られている」という先程と同じような問題にぶつかります(例えばコンテナの中身がコンテナだと出力できない)。他にも、std::pairstd::tuple の出力なんかもやりたいです。いくつかの解決方法が考えられますが{{< note std::vector や std::pair などを operator<< で出力できるようにしてしまうなど >}}、ここではさっきと同じように出力の部分を専用の関数に置き換えることで対応することにします。

コード
// 前略

#define debug(arg) print(#arg, arg)

// std::cerr << arg が元々使えるやつはそれを使う
template <class Tp> void out(Tp arg) {
  std::cerr << arg;
}
// std::pair の出力
template <class Tp1, class Tp2> void out(std::pair<Tp1, Tp2> arg) {
  std::cerr << '(';
  out(arg.first);
  std::cerr << ", ";
  out(arg.second);
  std::cerr << ')';
}
// std::tuple の出力
template <class T, std::size_t... Is> void print_tuple(T arg, std::index_sequence<Is...>) {
  static_cast<void>(((std::cerr << (Is == 0 ? "" : ", "), out(std::get<Is>(arg))), ...));
}
template <class... Ts> void out(std::tuple<Ts...> arg) {
  std::cerr << '(';
  print_tuple(arg, std::make_index_sequence<sizeof...(Ts)>());
  std::cerr << ')';
}
// std::{vector, deque, forward_list, list, initializer_list, set, multiset, unordered_set, unordered_multiset, map, multimap, unordered_map, unordered_multimap, valarray} の出力
template <template <class...> class Container, class... Ts>
void out(Container<Ts...> arg) {
  std::cerr << "[ ";
  std::for_each(std::cbegin(arg), std::cend(arg), [](typename Container<Ts...>::value_type elem) {
    out(elem);
    std::cerr << ' ';
  });
  std::cerr << ']';
}
// std::array の出力
template <class Tp, std::size_t N> void out(std::array<Tp, N> arg) {
  std::cerr << "[ ";
  std::for_each(std::cbegin(arg), std::cend(arg), [](Tp elem) {
    out(elem);
    std::cerr << ' ';
  });
  std::cerr << ']';
}

template <class Tp> void print(std::string_view name, Tp arg) {
  std::cerr << name << ": ";
  out(arg);  // out 関数を使うように変更
  std::cerr << '\n';
}

int main() {
  std::pair<int, int> p1 {100, 200};
  std::tuple<int, std::string, char> p2 {1000, "I am a string", 'x'};
  debug(p1);
  debug(p2);

  std::vector<std::pair<int, std::string>> X {{0, "hoge"}, {1, "fuga"}, {998244353, "piyo"}};
  std::set<std::tuple<int, int, int>> Y{{1, 2, 3}, {1, 2, 3}, {4, 5, 6}};
  debug(X);
  debug(Y);

  std::vector<std::vector<int>> Z {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
  std::vector<std::vector<std::vector<int>>> W {{{1, 2, 3, 4}, {5}}, {{6, 7}, {8, 9, 10}}};
  debug(Z);
  debug(W);
}
出力
p1: (100, 200)
p2: (1000, I am a string, x)
X: [ (0, hoge) (1, fuga) (998244353, piyo) ]
Y: [ (1, 2, 3) (4, 5, 6) ]
Z: [ [ 1 2 3 ] [ 4 5 6 ] [ 7 8 9 ] ]
W: [ [ [ 1 2 3 4 ] [ 5 ] ] [ [ 6 7 ] [ 8 9 10 ] ] ]

ちゃんと出力できるようになりましたが、多次元のコンテナ{{< note ここでいう「コンテナ」は C++ 用語の「コンテナ」より広義です。例えば std::{stack, queue, priority_queue} は container adapter という方が正確でしょうし、std::{initializer_list, valarray} も普通コンテナとは呼びませんが、ここでは気にしていません。 >}}の出力が見づらいです。コンピューターの画面は 2 次元なので 3 次元以上のコンテナの出力の可読性は諦めることにして、2 次元のコンテナの出力をもう少しマシにしたいです。そのために、コンテナの要素がコンテナであるかどうか判定したいです。

まず、変数が std::cbegin() の引数になれるかどうかも調べて、変数が走査可能なコンテナ{{< note 例えば std::stack はここでいう「コンテナ」ではありますが、走査可能ではありません。 >}}かどうかを調べます。

次に、ここでは変数の型がメンバ型 value_type を持っているかを調べることで変数がコンテナであるかどうかを調べます{{< note 例えば std::vector<int>::value_type は int 型の別名 (member type) として定義されます。一方、int::value_type は存在しません。 >}}。ただし std::stringstd::string_view は実用上の理由から除外します{{< note "fox" が [ f o x ] と出力されてもあまり嬉しくない >}}。更に、int[] などの value_type を持たない生配列もここではコンテナであると判定したいので、走査可能なコンテナであると判定されている場合はコンテナであるということにします。

また、これ以降のコードを簡略化するためにコンテナの要素の型を取得できるようにして、コンテナの要素がコンテナであるかを判定して 2 次元以上のコンテナであるかどうかも判定しておきます。

コード
// 前略

#define debug(arg) print(#arg, arg)

// 中略

template <class Tp> auto has_cbegin(int)     -> decltype(std::cbegin(std::declval<Tp>()), std::true_type {});
template <class Tp> auto has_cbegin(...)     -> std::false_type;
template <class Tp> auto has_value_type(int) -> decltype(std::declval<typename Tp::value_type>(), std::true_type {});
template <class Tp> auto has_value_type(...) -> std::false_type;

// 走査可能なコンテナかどうかを表す変数 is_iterable_container_v を作る
// ただし std::string, std::string_view では false にする
template <class Tp> constexpr bool is_iterable_container_v                   = decltype(has_cbegin<Tp>(int {}))::value;
template <>         constexpr bool is_iterable_container_v<std::string>      = false;
template <>         constexpr bool is_iterable_container_v<std::string_view> = false;

// メンバ型 value_type を持っているかどうかを見て、コンテナかどうかを表す変数 is_container_v を作る
// これは必ずしも走査可能なコンテナでなくてもよい (stack, queue, priority_queue なども true にしたい)
// ただし std::string, std::string_view では false にする
template <class Tp> constexpr bool is_container_v                   = is_iterable_container_v<Tp> || decltype(has_value_type<Tp>(int {}))::value;
template <>         constexpr bool is_container_v<std::string>      = false;
template <>         constexpr bool is_container_v<std::string_view> = false;

// elem_t<Tp> でコンテナ Tp の要素の型を取得できるようにする (Tp がコンテナでない場合には void とする)
template <class Tp, std::enable_if_t<!decltype(has_value_type<Tp>(int {}))::value, std::nullptr_t> = nullptr>
  auto check_elem(int) -> decltype(*std::cbegin(std::declval<Tp>()));
template <class Tp, std::enable_if_t<decltype(has_value_type<Tp>(int {}))::value, std::nullptr_t> = nullptr>
  auto check_elem(int) -> typename Tp::value_type;
template <class Tp>
  auto check_elem(...) -> void;

template <class Tp> using elem_t = decltype(check_elem<Tp>(int {}));

// 多次元のコンテナであるかどうかを表す変数 is_multidim_container_v を作る
template <class Tp> constexpr bool is_multidim_container_v = is_container_v<Tp> && is_container_v<elem_t<Tp>>;

// コンテナ用の out 関数
template <class Container>
std::enable_if_t<is_iterable_container_v<Container>> out(Container arg) {
  if (std::distance(std::cbegin(arg), std::cend(arg)) == 0) {
    std::cerr << "<empty container>";
    return;
  }
  std::cerr << "[ ";
  std::for_each(std::cbegin(arg), std::cend(arg), [](elem_t<Container> elem) {
    out(elem);
    std::cerr << ' ';
  });
  std::cerr << ']';
}

// コンテナではない変数 または 1 次元のコンテナ を名前とともに出力
template <class Tp> std::enable_if_t<!is_multidim_container_v<Tp>>
print(std::string_view name, Tp arg) {
  std::cerr << name << ": ";
  out(arg);
  std::cerr << '\n';
}

// 多次元のコンテナを名前とともに出力
template <class Tp> std::enable_if_t<is_multidim_container_v<Tp>>
print(std::string_view name, Tp arg) {
  std::cerr << name << ": ";
  if (std::distance(std::cbegin(arg), std::cend(arg)) == 0) {
    std::cerr << "<empty multidimensional container>\n";
    return;
  }
  std::for_each(std::cbegin(arg), std::cend(arg),
    [&name, is_first_elem = true](elem_t<Tp> elem) mutable {
      if (is_first_elem)
        is_first_elem = false;
      else
        for (std::size_t i = 0; i < name.length() + 2; i++)
          std::cerr << ' ';
      out(elem);
      std::cerr << '\n';
  });
}

int main() {
  std::vector<std::vector<int>> Z {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
  std::vector<std::vector<std::vector<int>>> W {{{1, 2, 3, 4}, {5}}, {{6, 7}, {8, 9, 10}}};
  debug(Z);
  debug(W);
}
出力
Z: [ 1 2 3 ]
   [ 4 5 6 ]
   [ 7 8 9 ]
W: [ [ 1 2 3 4 ] [ 5 ] ]
   [ [ 6 7 ] [ 8 9 10 ] ]

コンテナの出力には以下のようなコードで十分だと思われるかもしれませんが、

template <class Container> void out(Container arg) {
  std::cerr << "[ ";
  for (std::size_t i = 0; i < arg.size(); i++) {
    out(arg[i]);
    std::cerr << ' ';
  }
  std::cerr << ']';
}

などの問題があり、十分な抽象化になっていません。ここで採用したコードではコンテナ A に対して std::cbegin(A), std::cend(A) が利用できることしか要求していないので、この要件さえ満たせば標準ライブラリのコンテナに限らず生の配列 int[] や外部のライブラリのコンテナ boost::{deque, flat_set, flat_map}, __gnu_pbds::{tree, gp_hash_table} などの様々な「配列っぽいもの」を出力することができます。

ただし std::{stack, queue, priority_queue} はイテレータで要素にアクセスすることができないので、個別に対応します。中身を一つずつ取り出して出力し、最終的に空にしてしまうので引数を参照で受け取ってはいけません。

コード
#include <...>

#define debug(arg) print(#arg, arg)

// 中略

template <class... Ts> void out(std::stack<Ts...> arg) {
  if (arg.empty()) {
    std::cerr << "<empty stack>";
    return;
  }
  std::cerr << "[ ";
  while (!arg.empty()) {
    out(arg.top());
    std::cerr << ' ';
    arg.pop();
  }
  std::cerr << ']';
}
template <class... Ts> void out(std::queue<Ts...> arg) {
  if (arg.empty()) {
    std::cerr << "<empty queue>";
    return;
  }
  std::cerr << "[ ";
  while (!arg.empty()) {
    out(arg.front());
    std::cerr << ' ';
    arg.pop();
  }
  std::cerr << ']';
}
template <class... Ts> void out(std::priority_queue<Ts...> arg) {
  if (arg.empty()) {
    std::cerr << "<empty priority_queue>";
    return;
  }
  std::cerr << "[ ";
  while (!arg.empty()) {
    out(arg.top());
    std::cerr << ' ';
    arg.pop();
  }
  std::cerr << ']';
}

// 中略

int main() {
  std::stack<int> A, B;
  B.emplace(1);
  B.emplace(2);
  B.emplace(3);
  debug(A);
  debug(B);

  std::stack<std::pair<int, std::string>> C;
  C.emplace(1, "First");
  C.emplace(2, "Second");
  debug(C);

  std::queue<std::pair<int, std::string>> D;
  D.emplace(1, "First");
  D.emplace(2, "Second");
  debug(D);

  std::priority_queue<int> E;
  E.emplace(1);
  E.emplace(100);
  E.emplace(10);
  debug(E);

  std::priority_queue<int, std::vector<int>, std::greater<int>> F;
  F.emplace(1);
  F.emplace(100);
  F.emplace(10);
  debug(F);
}
出力
A: <empty stack>
B: [ 3 2 1 ]
C: [ (2, Second) (1, First) ]
D: [ (1, First) (2, Second) ]
E: [ 100 10 1 ]
F: [ 1 10 100 ]

これもテンプレートで抽象化することはできなくはないと思いますが、先頭の要素を指すメンバ関数の名前が top() であったり front() であったりという微妙な違いがあり、それに対処すると却ってややこしくなるので std::{stack, queue, priority_queue} それぞれについて 3 つ並べて書きました。

あとは、複数の引数をとることができるようにこのマクロを改造していきます。複数の変数をどのように出力するかは好みが分かれると思いますが、ここではコンテナの前後では改行してそれ以外では改行しないようにします。つまり、

std::vector<int> A;

for (int i = 0; i < 2; i++) {
  for (int j = 0; j < 2; j++) {
    A.emplace_back(i * 2 + j);
    debug(i, j, A);
  }
}

と書いたときに

i: 0 | j: 0
A: [ 0 ]
i: 0 | j: 1
A: [ 0 1 ]
i: 1 | j: 0
A: [ 0 1 2 ]
i: 1 | j: 1
A: [ 0 1 2 3 ]

のように出力させます。

コード
#include <algorithm>


#define debug(...) multi_print(#__VA_ARGS__, __VA_ARGS__)

// 中略

// コンテナではない変数 または 1 次元のコンテナ を名前とともに出力
template <class Tp> std::enable_if_t<!is_multidim_container_v<Tp>>
print(std::string_view name, Tp arg) {
  std::cerr << name << ": ";
  out(arg);
  // 出力する変数がコンテナの場合にしか改行しないように変更
  if constexpr (is_container_v<Tp>)
    std::cerr << '\n';
}

// 中略

template <class Tp, class... Ts> struct first_element { using type = Tp; };
template <class... Ts> using first_t = typename first_element<Ts...>::type;

template <class Tp, class... Ts> void multi_print(std::string_view names, Tp arg, Ts... args) {
  if constexpr (sizeof...(Ts) == 0) {  // args... のパラメータが 0 個のとき、単に arg だけを print して終了
    // 変数名の終わりにある余分なスペース(あれば)を削除
    names.remove_suffix(
      std::distance(
        names.crbegin(),
        std::find_if_not(names.crbegin(), names.crend(),
                         [](const char c) { return std::isspace(c); })
      )
    );

    // arg を出力
    print(names, arg);

    // 最後に改行を出力する
    // ただし今出力した変数がコンテナである場合、既に print 関数によって改行が出力されているので何もしない
    if constexpr (!is_container_v<Tp>)
      std::cerr << '\n';
  } else {
    // names には全部の変数の名前が格納されているので、1 つ目の変数名の終わりの ',' の位置を探す
    std::size_t comma_pos = 0;

    for (std::size_t i = 0, paren_depth = 0, inside_quote = false; i < names.length(); i++) {
      if (!inside_quote && paren_depth == 0 && i > 0 && names[i - 1] != '\'' && names[i] == ',') {
        comma_pos = i;
        break;
      }
      if (names[i] == '\"') {
        if (i > 0 && names[i - 1] == '\\') continue;
        inside_quote ^= true;
      }
      if (!inside_quote && names[i] == '(' && (i == 0 || names[i - 1] != '\''))
        paren_depth++;
      if (!inside_quote && names[i] == ')' && (i == 0 || names[i - 1] != '\''))
        paren_depth--;
    }

    // 変数名の終わりにある余分なスペース(あれば)を削除
    const std::size_t first_varname_length = comma_pos - std::distance(
      names.crend() - comma_pos,
      std::find_if_not(
        names.crend() - comma_pos, names.crend(),
        [](const char c) { return std::isspace(c); }
      )
    );

    // 1 つ目の変数 (arg) を出力する
    // 1 つ目の変数名は names.substr(0, first_varname_length)
    print(names.substr(0, first_varname_length), arg);

    // 次に出力する変数がコンテナであるかどうかによって '\n' を出力するか " | " を出力するか変える
    // ただし今出力した変数 (arg) がコンテナである場合、既に print 関数によって改行が出力されているので何もしない
    if constexpr (!is_container_v<Tp>) {
      if constexpr (is_container_v<first_t<Ts...>>)
        std::cerr << '\n';
      else
        std::cerr << " | ";
    }

    // 次の変数名の始めに含まれる余分なスペース(あれば)を削除
    const std::size_t next_varname_begins_at = std::distance(
      names.cbegin(),
      std::find_if_not(
        names.cbegin() + comma_pos + 1, names.cend(),
        [](const char c) { return std::isspace(c); }
      )
    );
    names.remove_prefix(next_varname_begins_at);

    // この関数を再帰的に呼んで残りの変数も出力する
    multi_print(names, args...);
  }
}

int main() {
  std::vector<int> A;

  for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 2; j++) {
      A.emplace_back(i * 2 + j);
      debug(i, j, A);
    }
  }
}
出力
i: 0 | j: 0
A: [ 0 ]
i: 0 | j: 1
A: [ 0 1 ]
i: 1 | j: 0
A: [ 0 1 2 ]
i: 1 | j: 1
A: [ 0 1 2 3 ]

マクロで任意の個数の引数を受け取るときは ... を使い、__VA_ARGS__ で展開します(13 行目){{< note 例えば debug(a, b) は multi_print("a, b", a, b) に、debug(a,b,c+d) は multi_print("a,b,c+d",a,b,c+d) に展開されます。コードに含まれる空白もそのまま展開されることに注意が必要です。 >}}。あとは、関数の再帰呼び出しによって変数を出力すればよいです。

42 行目からの部分でカンマの位置を検索するのに std::distance(names.cbegin(), std::find(nemas.cbegin(), names.cend(), ',') とせずに for 文で検索を行っているのは

debug(std::min(A, B), C)                // std::min(A | B) | C と区切るべき?
debug(func1(','), A)                    // func1(' | ') | A ?
debug(func2(A, "Hello, world!"), B, C)  // func2(A | "Hello | world!) | B | C ?

みたいな呼び出しをしたときにカンマが出力してほしいものの終わりに必ずしも対応しないからです。文字を一つずつ読んで、かっこの深さが 0 かつ引用符の内側でない ‘,’ を見つけた場合にはそこを区切りと判定しています。もしかしたら考慮できていないケースがまだあるかもしれません。

ところで、このままでは std::pair<int, std::vector<int>> などを出力しようとするとエラーになります。何故なら、ファイルを上から読んでいったときに std::pair 用の out 関数の定義の時点ではコンテナ用の out 関数が宣言されていないからです。かといって std::pair 用の out 関数とコンテナ用の out 関数の定義の順序を入れ替えると今度は std::vector<std::pair<int, int>> の出力ができません。全てのケースでエラーを出さないために、最初に全ての out 関数のプロトタイプ宣言を書くことにします。それに伴って、is_iterable_container_v, is_container_v の宣言もファイルの上の方に移動します。

// 前略

#define debug(...) multi_print(#__VA_ARGS__, __VA_ARGS__)

template <class Tp> auto has_cbegin(int)     -> decltype(std::cbegin(std::declval<Tp>()), std::true_type {});
template <class Tp> auto has_cbegin(...)     -> std::false_type;
template <class Tp> auto has_value_type(int) -> decltype(std::declval<typename Tp::value_type>(), std::true_type {});
template <class Tp> auto has_value_type(...) -> std::false_type;

template <class Tp> constexpr bool is_iterable_container_v                   = decltype(has_cbegin<Tp>(int {}))::value;
template <>         constexpr bool is_iterable_container_v<std::string>      = false;
template <>         constexpr bool is_iterable_container_v<std::string_view> = false;
template <class Tp> constexpr bool is_container_v                   = is_iterable_container_v<Tp> || decltype(has_value_type<Tp>(int {}))::value;
template <>         constexpr bool is_container_v<std::string>      = false;
template <>         constexpr bool is_container_v<std::string_view> = false;

// 全ての out 関数のプロトタイプ宣言
template <class Tp> void out(Tp);
template <class Tp1, class Tp2> void out(std::pair<Tp1, Tp2>);
template <class... Ts> void out(std::tuple<Ts...>);
template <class C> std::enable_if_t<is_iterable_container_v<C>> out(C);
template <class... Ts> void out(std::stack<Ts...>);
template <class... Ts> void out(std::queue<Ts...>);
template <class... Ts> void out(std::priority_queue<Ts...>);

/* 以下略 */

これで目的のマクロを作成できましたが、新しい変数や関数をたくさん定義してしまったので namespace が汚れた上に、デバッグのためだけに 200 行以上もコードを記述してしまいました。また(デバッグ情報は標準出力ではなく標準エラー出力に出力されるためコメントアウトを忘れても誤った答えが標準出力に出力されることはありませんが、)デバッグマクロによって実行時間が増えないように手元の PC 以外ではデバッグ出力が実行されないようにしたいです。そこで、ここまでに書いたコードをまるまる新しい namespace にくるんで別ファイルに持っていくことにします。

今回作成した debug_print.hpp ではコードを namespace debug_print の内側に移動させ、更に出力先を debug_print::os という変数にしています(既定値は std::cerr で、標準エラー出力にデバッグ情報が出力されるようになっている)。出力先を変更したいときは debug_print.hpp 16 行目の std::ostream& os = std::cerr の右辺を変更すればよいです。

使い方

以下のようにマクロを LOCAL というマクロが定義されているかどうかによって debug(...) の挙動を変えるようにマクロを定義します。ローカル環境では、作成した debug_print::multi_print 関数を呼ぶようにします。

#include <algorithm>
#include <cctype>
#include <cstddef>
#include <iostream>
#include <iterator>
#include <string_view>
#include <type_traits>

#ifdef LOCAL
#  include <debug_print.hpp>
#  define debug(...) debug_print::multi_print(#__VA_ARGS__, __VA_ARGS__)
#else
#  define debug(...) (static_cast<void>(0))
#endif

int main() {
  int x, y, z;
  std::cin >> x >> y >> z;
  debug(x, y, z);  // #define LOCAL された環境でのみ実行される
}

コンテスト中に解答のコードをコンパイルするときには

g++ (または clang++) -std=c++17 (またはそれ以上) -I/path/to/dir -DLOCAL (他の引数)

のようにします。ただし、/path/to/dir の部分は debug_print.hpp が存在するディレクトリに置き換えてください。

おそらくそんなに長い時間が掛かることは無いと思いますが、コンパイルが遅くなるという場合にはヘッダーをプリコンパイルしてみてください。私は少しでもコンパイルの時間を短縮するためにヘッダーをプリコンパイルして使っています。

debug_print.hpp の中で #include されているファイルは必ずその外側(今回の例では 1-10 行目)でも同様に #include してください。そうでないと、(うっかり #include ディレクティブを書き忘れても debug_print.hpp 経由で include されている内容は利用できてしまうので)「手元の環境ではコンパイルできる解答がオンラインジャッジではコンパイルエラーになる」という事態になる可能性があります。(2022-08-03 追記: debug_print.hpp の中でヘッダを #include するのはやめて、必要なヘッダが #include されているか確認だけするように仕様を変更しました。使い方はこれまでと全く同じです。)

また、std::{string, tuple, stack, queue, priority_queue} を使う場合は debug_print.hpp よりも前にそのヘッダファイルを #include する必要があります。

#ifdef LOCAL
#include <debug_print.hpp>
#define debug(...) debug_print::multi_print(#__VA_ARGS__, __VA_ARGS__)
#else
#define debug(...) (static_cast<void>(0))
#endif

#include <stack>  // include するのが遅い!

int main() {
  std::stack<int> stk;
  debug(stk);  // error
}

一般的に C/C++ でプログラムを書く場合、このような仕様は良くありません。ヘッダファイルの中で他のヘッダファイルの機能を使用する場合はヘッダファイル内でそれを #include しておく方が良いと思います。しかし、debug_print.hpp を作る上では呼び出す元のソースファイルで #include されていないものを #include したいと思わなかったのでこのような仕様にしてあります{{< note まぁ競技プログラミングでは #include <bits/stdc++.h> を書いている人も少なくないし、このような仕様にする意味は薄いかもしれませんが。>}}。実用上は debug_print.hpp は最後に #include すれば問題無いと思います。

動作テスト(boost ライブラリを使用すると実行ファイルのサイズが Wandbox の制限を超えてしまったので boost ライブラリの検証の部分は飛ばされていますが、手元で実行するとちゃんと動きます。)

細かい話ですが、標準ライブラリに libc++ を使っていると std::valarray に対して std::cbegin() は使えないようなので、そのような標準ライブラリを使っていて std::valarray を競技プログラミングで使いたいと思っている人は debug_print.hpp 中の cbegin という単語をすべて begin に置換して使ってください。


このページに掲載しているコード及び debug_print.hpp は自由に使用していただいて構いませんが、使用によって生じたあれこれに関して私は一切の責任を負いかねます。

[See repo JSON]