そもそもrepマクロとは
競技プログラミングで入力文字数を減らすためにしばしば使われる、以下のようなマクロである。
#define REP(i, n) for (int i = 0; i < (n); i++)
repマクロのなにが問題か
そもそもC++ではマクロの使用自体が推奨されていない
現行のC++においてマクロは本当に必要でない限り利用すべきでない機能とされており、他の記法を使用できる限りそちらを使用するべきである。
C/C++のマクロの変換処理は構文を考慮しない
C/C++のマクロは、単に引数をテキスト置換した上で実コードに変換される。
重要な点として、マクロの変換処理はスコープを一切考慮しないため、例えばnamespace
などのステートメントで適用範囲を束縛することが出来ない。
本来C/C++のfor文は宣言からループ処理の記述を含めて一つのステートメントとされているが、構文規則を無視することで宣言部分のみを無理やり分割して変換しているため、注意しないと構文の整合性を損なう恐れがある。
引数n
をカッコで括っているのはこれを防ぐためである。
意地悪な例としては以下のようなものがある。
void solve() {
int a = 8;
REP(i = a, 3)
std::cout << i << std::endl;
std::cout << "Out: " << a << std::endl;
}
この関数の実行結果は以下のようになる。
1
1
1
Out: 3
この関数のループカウンタは正しく動作せず、変数a
は代入されているはずの8ではなく、3を返す。
マクロが以下のように変換されるからである。
for (int i = a = 0; i = a < (3); i = a++) // aの値が変更されている!
このfor文はループカウンタ変数i
の代わりに変数a
をインクリメントし、条件式を含む全ての式を実行するごとに、ループカウンタに値を代入してしまう。1
代わりになりそうな方法
boost::irangeを使う
Boost Libraryの中にirange
というループを処理するためのクラスがある。
for (int i : boost::irange(0, n)) {
// 0からn-1までのループ
}
第一引数に0
以外を指定すれば0
以外で始まるループも指定可能。
C++のラムダ式を使う
template<typename F> constexpr void rep(int n, F function) {
for (int i = 0; i < n; i++)
function(i);
}
以下のように書けるようになる。
rep(n, [&](int i) {
// do something
});
std::for_each
とほとんど同じように使えるので、タイプ数を減らすなら多分この方法が一番実用的。
クローリング中に見つけたのですが…
良記事です。ループの途中抜けに対応していたりと色々すごい。
番外編:while (n--)
古い環境でミリ秒単位の高速化をするために、C時代から使われていたループ。
元の変数そのものをループカウンタとして使用するため、値が変わってしまうのがネック。
当然ながら、値がマイナスだった場合は(実質的に)無限ループしてしまう。
while (n--) {
// do something
}
確かにタイプ量は少ないが…
最後に
多分そろそろRustに乗り換えたほうがいいです。
参考文献
- プログラミング言語C++ 第4版 - Bjarne Stroustrup, 柴田望洋, SBクリエイティブ
-
i
にカッコをつければ抑制できるが、GCCの場合、カッコが必要ないケースでwarningが出る。int{i}
と書けばwarningを抑制できます。 ↩