はじめに
皆さんは競技プログラミングで「テンプレート」は使っていますか?
rep
や chmin
、 modpow
など、よく使う処理を事前に用意したもののことです。
この記事では、C/C++で使える「テンプレート」1の記述方法について紹介します。
C/C++のテンプレートは邪魔!
テンプレートはとても便利です。コンテスト時に書くコードが少なくなり、解答スピードが速くなります。
実装方法を忘れてコンテスト中に調べ直す、なんてこともなくなります。
しかし、便利さを追い求めて様々な関数やクラスの追加を重ねるうちに、テンプレートが膨大な行数になってしまいがちです。
また、C/C++ではどうしても使うより先に宣言が必要です。すると、 main 関数はテンプレートの下に書かなくてはならず、いちばん下の main 関数にたどり着くまでのスクロールでイライラしてしまいます。
さらに、参考にしようとあなたの提出コードを読もうとした人が、いつまでも続くテンプレートを見て読むのを諦めてしまうかもしれません。
ヘッダファイルに分割しようにも、AtCoderなどの競技プログラミングサイトではフォルダでまとめて提出するといったことができません。
なので、すべて1つのファイルに書く必要があります。
ん? ヘッダファイル? 1つのファイルに書く? …………!
必殺!「自身をインクルード」
私、ひらめいちゃいました。
提出ファイルにソースファイル兼ヘッダファイルになってもらって、提出ファイルに提出ファイルをインクルードすればいいのです!
「ちょっと何言ってるかわからない。」
まあ、一度見てみてください。
必殺技の使い方
必殺技を使うには、下のように書きます。
テンプレートの部分は、自分用に書き換えて使ってください。
#ifndef INCLUDED_MAIN
#define INCLUDED_MAIN
#include __FILE__ // このファイル自体をインクルード
int main()
{
// 解答コード
}
#else // INCLUDED_MAIN
// ↓テンプレート↓
#include <iostream>
#include <vector>
using namespace std;
using ll = long long;
#define rep(i, n) for (int i = 0; i < (n); ++i)
#define all(x) begin(x), end(x)
template <class T>
bool chmax(T &a, const T &b) {
if (a < b) a = b; return a < b;
}
template <class T>
bool chmin(T &a, const T &b) {
if (b < a) a = b; return b < a;
}
// ↑テンプレート↑
#endif // INCLUDED_MAIN
main 関数がテンプレートより上に来ています!
これでテンプレートをどれだけ増やしても、ファイルやリンクを開くとすぐに main 関数が読めます!
必殺技の解説
ポイントはプリプロセッサによる条件分岐です。インクルードガードと同じような方法を使っています。
#ifndef INCLUDED_MAIN
// ①
#else
// ②
#endif
最初の行の #ifndef
の条件分岐で、 INCLUDED_MAIN
マクロが定義されていないときは①のコードだけ読まれ、すでに定義されているときは②のコードだけ読まれます。
#define INCLUDED_MAIN
// INCLUDED_MAIN が定義されているので
#include __FILE__ // ←ここでは②だけ読まれる
そして、①の部分の最初に INCLUDED_MAIN
を定義してから、自身をインクルードします。 __FILE__
は自身のファイル名2の文字列リテラルに展開される組み込みマクロです。
すると、先ほど解説した条件分岐で②の部分だけ読まれます。
ということは、①の部分はソースファイル、②の部分はヘッダファイルとして扱えます。
したがって、①の部分に main 関数を、②の部分にテンプレートを書けばいいのです。
これでテンプレートが main 関数の下にあってもコンパイルが通ります!
もっと main 関数を上に!
ここまでは main 関数の上にテンプレートをインクルードしてきましたが、テンプレートの下に main 関数をインクルードしても、展開後のコードは変わりません。
なので、次のようにも書くことができ、これなら2行目から main 関数を書き始めることができます!
#ifdef INCLUDED_MAIN
int main()
{
// 解答コード
}
#else
// テンプレート
#define INCLUDED_MAIN
#include __FILE__
#endif
Codeforces で使う方法
Codeforces では、提出したコードに #include __FILE__
もしくはそれに相当するような文字列が含まれていると、 Judgement failed となってジャッジされません。
しかし、 __FILE__
マクロを使うかわりに、直接ファイル名をコードに書くことで、 Codeforces でも使うことができます!
Codeforces のジャッジ時のファイル名は、 Clang なら p71.cpp 、それ以外なら program.cpp なので、 FILENAME
マクロをそれにあうように定義して #include FILENAME
と書くと、 #include __FILE__
と同じように展開されます。
また、ローカルでのファイル名を自由に決められるように、ジャッジ環境で定義されている ONLINE_JUDGE
マクロが定義されていないときは、 FILENAME
を __FILE__
と定義します。
#ifdef INCLUDED_MAIN
int main()
{
// 解答コード
}
#else
// テンプレート
#ifdef ONLINE_JUDGE
# ifdef __clang__
# define FILENAME "p71.cpp"
# else
# define FILENAME "program.cpp"
# endif
#else
# define FILENAME __FILE__
#endif
#define INCLUDED_MAIN
#include FILENAME
#endif
※ マクロを使わず #include "program.cpp"
のように書くと、 Judgement failed になります。
※ このコードは、ジャッジ時のファイル名に依存するため、今後使えなくなる可能性があります。
__INCLUDE_LEVEL__ を使う方法
@fujitanozomu さんにコメントで __INCLUDE_LEVEL__
について教えていただきました。
これはGCCやClangなどで使用できるマクロで、インクルードの深さを表す整数に展開されます。
直接読まれるときは 0
、インクルードされたときは 1
、インクルードされたファイルにインクルードされたときは 2
といった感じです。
これは、自身をインクルードしたときでも使えます。
__INCLUDE_LEVEL__
は、はじめて読まれるときは 0
、インクルードされて再度読まれるときは 1
に展開されます。
このことを利用して、 __INCLUDE_LEVEL__
が使用できる環境では、よりスマートに必殺技が使えます。
#if !__INCLUDE_LEVEL__
#include __FILE__
int main()
{
// 解答コード
}
#else
// テンプレート
#endif
INCLUDED_MAIN
といった条件分岐用のマクロを定義する必要がなくなるので、シンプルで読みやすくなります。
おわりに
これで問題を解く効率が少しはよくなると思います。3
C/C++使いの方はぜひご活用ください。