未来に飛ぶ

この記事は, Muroran Institute of Technology Advent Calendar 2019 15日目 の記事です(普通に忘れていました).

はじめに

1年前に競プロ用のデバグ出力マクロを作成したのですが,SFINAE初学者だった私のコードは見るに堪えないものでしたhttp://chut1130m.hatenablog.com/entry/2019/01/04/111047
今年度に入り,人に教える機会を頂くことで自分の理解が進んだこと,またC++20によってconceptという機能が導入されたことがあり,新しく作り直すことにしました.特にconceptの機能がすさまじく,圧倒的に可読性や理解のしやすさが上がっているので,ぜひこの記事を見た方も自作してみてはいかがでしょうか.
※2019/12/20時点でg++ 10.0のみ動作を確認しています.それ以外ではまだ動きません,コンパイラの成長を待ちましょう.

concept

前回との大きな違いがこのconceptです.
conceptはC++20より導入された(される予定の)テンプレートパラメータを制御する機能です.型に対して制約を付与することができるようになり,従来のSFINAEをより簡潔にできるようになります.
例えば,「型はクラスである」という制約は以下のように宣言します.

template <typename T>
concept isClass = std::is_class_v<T>;

作成した制約はtemplate宣言時の"class"や"typename"の代わりに制約を配置することで使うことが出来ます.下記コードでは引数がクラスかそうでないかのオーバーロードが非常に簡潔に記述できています.

template <isClass T>
auto func(T t) {
  std::cout << "is class" << std::endl;
}

auto func(...) { 
  std::cout << "is not class" << std::endl;
 }

さらに,requires式等を用いることでより複雑な制約を作ることもできます. 詳細はcppreferenceより確認してください.
「型Gの元について加算演算が定義されている」という制約は以下のように作成できます.従来ではややこしい処理が必要でしたが,conceptでは視覚的にも分かりやすく記述できます.

template <class T>
concept Addable = requires(T x, T y) {
  x + y;
};

template <Addable T>
auto func(T t) {
  std::cout << "addable" << std::endl;
}

auto func(...) { 
  std::cout << "not addable" << std::endl;
 }

なお,オーバーロード部分はC++20によってより今風でおしゃれな記述にすることが出来ます.これ以降は全てこの表記を使用していきます.※これがMSVCでまだ使えません(2019/12/20).

auto func(Addable auto t) {
  std::cout << "addable" << std::endl;
}

auto func(...) { 
  std::cout << "not addable" << std::endl;
 }

構築

実際にデバグ用のマクロを構築していきます.

オーバーロード:コンテナ

まずはプリミティブ型とコンテナ型とで出力関数を切り替えられるようにします.これはconceptを用いることで簡単に実現できます.

// 制約「begin関数を持つ」(==コンテナである)
template <class T>
concept Container = requires(T x) {
  x.begin();
};

// コンテナの出力
constexpr auto print(const Container auto& c) {}
// それ以外の出力
constexpr auto print(const auto& p) {}

オーバーロード:その他

コンテナ型とプリミティブ型との切り替えはできましたが,そもそも出力できない(ostreamとの<<演算子が定義されていない)変数を受け取った場合にプリミティブ型の関数が呼ばれてしまいエラーが起きます.出力できない変数を受け取る処理はconceptで簡単にできるので,今の内に除いておきます.

// 制約「begin関数を持つ」(==コンテナである)
template <class T>
concept Container = requires(T x) {
  x.begin();
};

// 制約「ostreamとの<<演算子が定義されている」(==出力可能である)
template <class T>
concept Printable = requires(T x) {
  std::cout << x;
};

// コンテナの出力
constexpr auto print(const Container auto& c) {}
// それ以外の出力
constexpr auto print(const Printable auto& p) {}
// 出力できない場合
auto print(const auto&) {}

変数名の取得

前回と同様に変数の値だけでなく変数名も表示したいので,プリプロセッサマクロで変数名をキャッチします.同時に複数の変数を可変引数によって受け取り,それぞれ一つずつ処理できるような機構を作成します.詳細は前回の記事を参考にしてください.

// debug用出力マクロ
#define dump(...)                                               \
  do {                                                          \
    auto __DUMP_NAME_LIST__ = split(#__VA_ARGS__, ',');         \
    splitVariables(std::move(__DUMP_NAME_LIST__), __VA_ARGS__); \
  } while (false)

// 1変数ずつ処理
constexpr auto splitVariables(auto&& names) {}
constexpr auto splitVariables(auto&& names, const auto& x, const auto&... tail) {
  str.pop_front();
  splitVariables(std::forward<decltype(names)>(names), tail...);
}

変数名の出力

変数の値を出力する関数は出来ているので,変数名と値をセットで出力する関数を間にかませます.これにより,「変数受け取り->変数名と値を分離->それぞれを出力」という一連の流れができました.

// 変数の出力
constexpr auto printVariable(auto&& name, const auto& p) {
  std::cout << name << ": ";
  print(p);
  std::cout << '\n';
}
constexpr auto printVariable(auto&& name, const Container auto& c) {
  std::cout << "-- " << name << " --" << '\n';
  print(c);
}

特殊化:string

現状コンテナの判定をbegin関数の有無で判定しているため,string型もコンテナとして判定されています.文字列が1文字ずつ区切られて出力されるのは違和感があるので,特殊化によってstring専用の出力関数を定義します.※concept部分の制約をいじるのも有

inline auto print(const std::string& s) { std::cout << s << ' '; }

inline auto printVariable(auto&& name, const std::string& s) {
  std::cout << name << ": ";
  print(s);
  std::cout << '\n';
}

動作確認

完成したので動作確認をします.
前者のコードは前回のテストコードです.同様の出力がされていることが確認できます.後者のコードは前回のコードの課題であった,"3次元以上のコンテナの出力が上手くいかない","出力できない変数を渡すとコードが止まる"という問題が解決できているか確認しています.出力を見ると解決できていることが分かりますね.

#include <string>
#include <vector>
#include "(デバグマクロのヘッダ)"

signed main() {
  const auto num = 2LL;
  const auto str = "hello";
  const std::vector<int> vec{1, 2, 3};
  const std::list<char> lst{'a', 'b', 'c'};
  const std::vector<std::vector<double>> mat = {{1.1, 2.3}, {5.6, 1.2}};

  // デバグ出力
  dump(num, str, vec, lst, mat);
}
num: 2
str: hello
-- vec --
1 2 3
-- lst --
a b c
-- mat --
1.1 2.3
5.6 1.2
#include <list>
#include  "(デバグマクロのヘッダ)"

using std::list;
class MyClass {};

signed main() {
  list<list<list<int>>> lst3{
      {{1, 1}, {2, 2}}, {{3, 3, 3}}, {{4}, {5}, {6}, {7}}};
  auto myobj = MyClass();

  dump(lst3, myobj);
}
-- lst3 --
1 1
2 2

3 3 3

4
5
6
7

myobj: <ERROR!> "print" of This type is not defined.

おわりに

未来に飛びすぎて自分の競プロ環境で動きませんでした.MSVCはよ.
C++20サイキョウ!

最終版

#include <concepts>
#include <iostream>
#include <list>
#include <string_view>

// debug用出力マクロ
#define dump(...)                                               \
  do {                                                          \
    auto __DUMP_NAME_LIST__ = split(#__VA_ARGS__, ',');         \
    splitVariables(std::move(__DUMP_NAME_LIST__), __VA_ARGS__); \
  } while (false)

// split
inline auto split(std::string_view str, char del = ' ') {
  std::list<std::string_view> sList;
  int from = -1;
  for (int i = 0; auto&& c : str) {
    if (c == ' ') {
      sList.emplace_back(str.substr(from + 1, i - from - 2));
      from = i;
    }
    ++i;
  }
  sList.emplace_back(str.substr(from + 1, str.size() - from));
  return sList;
}

// 制約
template <class T>
concept Container = requires(T x) {
  x.begin();
};

template <class T>
concept Printable = requires(T x) {
  std::cerr << x;
};

// 出力
constexpr auto print(const auto&) {
  std::cerr << "<ERROR!> \"print\" of This type is not defined." << '\n';
}
inline auto print(const std::string& s) { std::cerr << s << ' '; }
constexpr auto print(const Printable auto& p) { std::cerr << p << ' '; }
constexpr auto print(const Container auto& c) {
  for (auto&& x : c) {
    print(x);
  }
  std::cerr << '\n';
}

// 変数の出力
constexpr auto printVariable(auto&& name, const auto& p) {
  std::cerr << name << ": ";
  print(p);
  std::cerr << '\n';
}
inline auto printVariable(auto&& name, const std::string& s) {
  std::cerr << name << ": ";
  print(s);
  std::cerr << '\n';
}
constexpr auto printVariable(auto&& name, const Container auto& c) {
  std::cerr << "-- " << name << " --" << '\n';
  print(c);
}

// 1変数ずつ処理
constexpr auto splitVariables(auto&& names) {}
constexpr auto splitVariables(auto&& names, const auto& x,
                              const auto&... tail) {
  printVariable(names.front(), x);
  names.pop_front();
  splitVariables(std::forward<decltype(names)>(names), tail...);
}