LoginSignup
165
111

More than 1 year has passed since last update.

これで効率爆上がり!C/C++の競プロ用テンプレートをmain関数の下に追いやる必殺技

Last updated at Posted at 2021-03-24

はじめに

皆さんは競技プログラミングで「テンプレート」は使っていますか?
repchminmodpow など、よく使う処理を事前に用意したもののことです。
この記事では、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++使いの方はぜひご活用ください。

  1. C++のクラステンプレートや関数テンプレートの「テンプレート」ではないです。

  2. 絶対パスの場合もあります。

  3. タイトルの「爆上がり」は言い過ぎだと思います。

165
111
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
165
111