今日勉強したことを
つらつらと
logo

【C++】repマクロとは?使わないほうがいい?

2020/05/20 09:05
競技プログラミングで多用されるrepマクロですが、多用しないほうかいいのでは?

競技プログラミングをやってると、rep マクロを使っているコードによく出会います。1 文字でも入力を短くして入力をはやくして、バグの原因を減らすのが目的です。

マクロは書き方が自由なので、人によって書き方に違いが出てきてしまいます。私の感覚だと、あまり使いたくない機能です。

マクロとは

ソースの文字を別の文字に置き換えるようにできるものです。

#define 置き換える前の文字列 置き換える後の文字列で定義します。

#include <bits/stdc++.h>
using namespace std;
#define HELLO "hello, world!"
int main() {
    cout << HELLO;
}

と書くとコンパイル時にhello“hello, world!”に置き換えられます。上のソースと下のソースは同義です。

#include <bits/stdc++.h>
using namespace std;
int main() {
    cout << "hello, world!";
}

関数形式マクロ

単純な文字列の置き換えだけでなく、メソッドのような感じで引数みたいなものも使えます。 置き換える前の文字列の後ろにカッコを付けると、引数にみたいな形でマクロに渡せます。

#include <bits/stdc++.h>
using namespace std;
#define IF_HELLO(V) if((V) == "hello")
int main() {
    IF_HELLO("hello") {
        cout << "true";
    } else {
        cout << "false";
    }
}

とすると、下のソースに置き換えられます。

#include <bits/stdc++.h>
using namespace std;
int main() {
    if(("hello") == "hello) {
        cout << "true";
    } else {
        cout << "false";
    }
}

文字列を置き換えるだけ

関数の定義のように見えるけど、単純に文字列を置き換えているだけです。 なので、こんな不気味な書き方もできちゃいます。

#include <bits/stdc++.h>
using namespace std;
// カッコ始まりだけ書いてみる
#define IF_HELLO(V) if((V) == "hello") {
int main() {
    // マクロで始まりが書いてあるから、始まりは書かない
    IF_HELLO("hello")
        cout << "true";
    } // 閉じカッコは書く
}

rep マクロとは

APG4bAP4 - 付録 4.ループの裏技 rep マクロという記事があるので、こちらを参照してください。

便利は便利です。

表記がブレる

APG4b で紹介されている rep マクロは ↓ です。

#define rep(i, n) for (int i = 0; i < (int)(n); i++)

私が普段使用している rep マクロは ↓ です。

#define REP(i, n) for (int i = 0; i < (int)(n); i++)

マクロは UPPER CASE にする場合が多いですが、競プロの rep マクロは小文字にしている方が多いです。なるべく普通の書き方をしたいので、私は大文字にしています。同じ機能で同じ書き方をしたいのに、表記がブレてしまうのがいただけないです・・

C 言語の書き方であること

C++はオブジェクト指向プログラミング言語です。rep マクロは C 言語に寄った書き方なので、C++っぽい書き方をしたほうが好ましいです。

例えば rep マクロを書きたい頻出シーンは、リストすべてを全探索したい場合です。そういう場合は(範囲 for 文)[https://atcoder.jp/contests/apg4b/tasks/APG4b_r]を使いましょう。

#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<string> v = {"hoge", "fuga", "piyo", "foo", "bar"};
    for(auto&& str : v) {
        cout << str << endl;
    }
}

こういう問題が競技プログラミングで出題される場合、n に入力する数が設定され、その後に文字列が渡されるような入力になる場合が多いです。

5
hoge
fuga
piyo
foo
bar

その場合は先に vector を作っておいて、範囲 for 文で書くとわかりやすいです。

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<string> v(n);
    for(auto&& str : v) {
        cin >> str;
    }
    for(auto&& str : v) {
        cout << str << endl;
    }
}

この 2 パターンでまかなえないループは、初期化も条件も更新も複雑になものです。つまり rep マクロでは実現できません。

一周回って for が便利

範囲 for 文も使えるし、複雑な条件も書ける for 文が一周回って便利です。そして rep マクロにたより続けると C++らしい書き方の練習になりません。ぜひ拡張 for や、今回紹介しませんでしたが(イテレータ)[https://atcoder.jp/contests/apg4b/tasks/APG4b_ai]を使った for 文もあるので、ぜひ色んな書き方に親しんでみましょう!


© 2021 simodake