はじめに
初投稿です.
C++初心者で,最近競プロでC++をやっています.
C++がPythonと比べて不便だと思ったのは,vectorやmapがcoutできないため,デバッグがやりにくいということです.
Pythonみたいなprint関数をC++で書きたい!ではmapなどは対応していませんでした.
どんな型でもprintできるようにしたい!
そこで,つまづきながらもprint関数に似たものを作ることにしました.(初心者なのでコードが汚いかもしれませんが許してください)
Pythonのprint関数
print("Hello world!") # Hello world!
print({1: 2, 3: 4}) # {1: 2, 3: 4}
print({1: [1, 2], 2: [2, 3]}) # {1: [1, 2], 2: [2, 3]}
print({1, 2, 3}, (1, 2, 3)) # {1, 2, 3} (1, 2, 3)
このように,どのような型であっても,読みやすい形で返ってきます.
これを再帰的にC++で書きます.
C++の実装
表示
次のように表示されます:
int main() {
print("Hello world!"); // Hello world!
print(map<int, int>{{1, 2}, {3, 4}}); // {1: 2, 3: 4}
print(unordered_map<int, vector<int>{{1, {1, 2}}, {2, {2, 3}}}); // {1: [1, 2], 2: [2, 3]}
print(set<int>{1, 2, 3}, make_tuple(1, 2, 3)); // {1, 2, 3} (1, 2, 3)
}
型 | 表示例 |
---|---|
pair | (1, 2) |
tuple | (1, 2, 3) |
vector | [1, 2] |
map | {1: 2, 2: 3} |
set | {1, 2} |
unordered_map | {1: 2, 2: 3} |
unordered_set | {1, 2} |
複数の型の組み合わせでもちゃんと動きます.
コード
# include <bits/stdc++.h>
using namespace std;
// Outというクラスを作ることで関数定義の順番に関係なくoutが使える
// out関数は改行なし
struct Out {
// type_traitsを使って小数とそれ以外に分け,小数のみ小数点以下桁数指定をする
// 小数以外
template<class T, enable_if_t<!is_floating_point_v<T>>* = nullptr>
static void out(const T& a) noexcept {
cout << a;
}
// 小数
template<class T, enable_if_t<is_floating_point_v<T>>* = nullptr>
static void out(const T& a) noexcept {
cout << fixed << setprecision(7) << a << defaultfloat;
}
// pair
template<class T, class U>
static void out(const pair<T, U>& a) noexcept {
cout << '(';
out(a.first);
cout << ", ";
out(a.second);
cout << ')';
}
// tuple
/*
size_t iを何番目を出力するかの変数としてもつ(これはコンパイル時に決まる)
tuple_size<T>::valueでTのsizeがわかるので,いつ終了するか決められる
*/
template<size_t i = 0, class T>
static void iterate_tuple(const T& a) noexcept {
if constexpr (tuple_size<T>::value == 0) return;
out(get<i>(a));
if constexpr (i + 1 != tuple_size<T>::value) {
cout << ", ";
iterate_tuple<i + 1>(a);
}
}
// 括弧をつける
template<class... Args>
static void out(const tuple<Args...>& a) noexcept {
cout << '(';
iterate_tuple(a);
cout << ')';
}
// 以下は全て似たようなコード
// vector
template<class T>
static void out(const vector<T>& a) noexcept {
cout << '[';
if (!a.empty()) {
for (auto i = a.begin(); ; ) {
out(*i);
if (++i != a.end()) cout << ", ";
else break;
}
}
cout << ']';
}
// map
template<class T, class U>
static void out(const map<T, U>& a) noexcept {
cout << '{';
if (!a.empty()) {
for (auto i = a.begin(); ; ) {
out(i->first);
cout << ": ";
out(i->second);
if (++i != a.end()) cout << ", ";
else break;
}
}
cout << '}';
}
// unordered_map
template<class T, class U>
static void out(const unordered_map<T, U>& a) noexcept {
cout << '{';
if (!a.empty()) {
for (auto i = a.begin(); ; ) {
out(i->first);
cout << ": ";
out(i->second);
if (++i != a.end()) cout << ", ";
else break;
}
}
cout << '}';
}
// set
template<class T>
static void out(const set<T> &a) noexcept {
cout << '{';
if (!a.empty()) {
for (auto i = a.begin(); ; ) {
out(*i);
if (++i != a.end()) cout << ", ";
else break;
}
}
cout << '}';
}
// unordered_set
template<class T>
static void out(const unordered_set<T>& a) noexcept {
cout << '{';
if (!a.empty()) {
for (auto i = a.begin(); ; ) {
out(*i);
if (++i != a.end()) cout << ", ";
else break;
}
}
cout << '}';
}
};
// print関数
// 改行のみ
inline void print() noexcept {
cout << '\n';
fflush_unlocked(stdout); // flush
}
// 引数一つ(間のスペースなし)
template<class T>
inline void print(T& a) noexcept {
Out::out(forward<T>(a));
cout << '\n';
fflush_unlocked(stdout); // flush
}
// 二個以上の可変長引数(間にスペースを入れる)
template<class Head, class... Args>
inline void print(Head&& head, Args&&... args) noexcept {
Out::out(forward<Head>(head));
cout << ' ';
print(forward<Args>(args)...);
}
説明
out関数
まず,out関数というprintの改行をなくしたものを作ります.(改行は最後のみ行いたいからです)
out関数には,引数の型によって色々な出力を定義します.例えばvectorなら,
template<class T>
void out(const vector<T>& a) noexcept {
cout << '[';
if (!a.empty()) {
for (auto i = a.begin(); ; ) {
out(*i); // ここをoutにしているため中身はどんな型でも良い
if (++i != a.end()) cout << ", ";
else break;
}
}
cout << ']';
}
というようにして,中身がどのような型でも対応できるようにしておきます.これでout関数がまた呼び出され,出力されることになります.
Outクラス
ここで問題なのが,C++はPythonと違って後ろの関数を呼び出せないことです.
例えば,vector<set<int>>
の型をoutするとき,vector<T>
が先に定義されていると,set<T>
を呼び出せなくなってしまいます.
しかし,クラス内の関数だと,あとで定義した関数を呼び出せるようなので,これを利用します.(後の関数を呼び出せるプロトタイプ宣言がありますが,大量のプロトタイプ宣言を書くよりはクラスの方が楽です.)
Outというクラス内にout関数を全部入れてやればできます.
print関数
あとは,最後に改行を入れたprint関数を定義します.print(1, 2)
みたいにできるように,可変長引数で定義します.これはこの記事とほぼ同じです(ただ,最後のスペースを出力しないための処理が少し違います)→Pythonみたいなprint関数をC++で書きたい!
細かいところ
- static
- staticをつけるとオブジェクト生成をせずにメンバ関数を呼び出せるらしい
- ユニバーサル参照
-
T&&
はユニバーサル参照で,右辺値の場合は右辺参照,左辺値の場合は左辺参照となる -
std::forward
は,ユニバーサル参照時に右辺参照したものは右辺参照になり(std::move
と同じ),左辺参照したものは左辺参照となる
-
- 可変長引数
-
template<class... Args>
とすると全ての引数がまとまったテンプレートになる -
template<class Head, class... Args>
というようにして最初だけを処理して,あとは再帰的に書く - https://marycore.jp/prog/cpp/variadic-function/ が参考になる
-
-
type_traits
- 型が小数かでオーバーロードできたりするので便利
-
template<class T, enable_if_t<is_floating_point_v<T>>* = nullptr>
はおまじない -
type_traits
ヘッダに他にもいろいろある
おわりに
デバッグに便利かもしれません.