えびちゃんの日記

えびちゃん(競プロ)の日記です。

C++ の違法識別子について

警察だ!(インターネット meme)

以下の正規表現で表される識別子は我々は勝手に使ってはいけません. 処理系に予約されています.コンパイラの内部利用や標準のヘッダ(<algorithm> とか)で使うためのものです.

  • .*__.*
  • _[A-Z].*
  • _.*(グローバル名前空間のみ)

正規表現がわからない人向けに例を挙げると,以下のようなものが該当します.

  • __foo
  • ____foo
  • f__oo
  • foo__
  • _Foo
  • _foo(グローバル名前空間のみ)

foo_ などは許されています.

他にも,STL のヘッダを一つでも #include した場合,いずれかの(すなわち直接 #include しているかは関係なく)ヘッダで宣言されている名前を #define するのは未定義([macro.names] に書いてある)です.

それから,keyword と一致するものもだめですね.

なんで予約されてるの

標準ヘッダに書かれているものも当然プログラムなので,変数などを宣言したりしますね. そんなときに,処理系によって変数名がまちまちだったらどうなるでしょう?

#define hello_cplusplus
#include <cstdio>

ある環境の <cstdio> では hello_cplusplus という変数が使われていて上のコードがエラーになったり,そうでない環境ではコンパイルできたり... というのは困りますね. 逆に,今の処理系でコンパイルできるものが(未定義などを含んでいないにも関わらず)別の処理系でコンパイルできないのはいやですね.

それから,C++ には関数のオーバーロードがありますね.

int f() { ... }
int f(int x) { ... }
int f(double x) { ... }

など同名の関数が定義できるアレです.内部的には,これらはそれぞれ __Z1fv, __Z1fi, __Z1fd のような名前で扱われたりします.

そんなわけで,仕様で規定されている名前を除いて,処理系が自由に使っていい名前と我々が使える名前が分かれています.

逆に,我々が勝手に使えるはずの名前を #define するとこわれる処理系は不適格とされるはずです.

いつもの

さて,これらの識別子を勝手に使った場合の動作は未定義なんですよね. 少し遊んでみましょうか.

#define _STDIO_H
#define _STDIO_H_
#define __STDIO_H  // 念のためいろいろやっておく
#include <cstdio>

int main() { puts("Hello"); }

どうですか? たくさんエラーメッセージが出てきませんか? インクルードガードと衝突するためですね.

#define c
#include <queue>

int main() {}

これもだめでしょう.は?って感じですね. std::queue のメンバ変数に c という名前が存在することが規定されています. これどうにかならなかったんですかね.

#define int long long

実は未定義っぽいというアレ.

GCC だと大丈夫と 文書化 されているので大丈夫な気もします)

rsk0315.hatenablog.com

この記事にもありますが,ちゃんと動いてそうに見えても未定義は未定義です.何が起きても知らないですからね.

int _Z1fv = 0;
int f() { return 42; }

invalid symbol redefinition とか出てくる.事情を知らないと意味不明そう.

ところで,我々が普段目にするもので,この予約された名前がいくつかありますね?

__builtin_popcount

__builtin_* 系の関数です.GCC の組み込み関数たちですね.

gcc.gnu.org

知らないものもたくさんあるでしょう?

ちゃんと文書化されていますし,GCC を前提とするなら使っても問題ないでしょう.

std::__gcd

闇の競プロ er がよく使っていますね. これは,<algorithm> の中で rotate の実装のために用意されたヘルパ関数であるみたいなことがコメントで書かれています.なんですが,使われていなさそう? わからない.

文書化されておらず,内部利用のためのものなので,積極的には使わない方がいいのでは?と思っています.

std::bitset::_Find_next

std::bitset<128> bs;
bs._Find_next(n);

などとすると n 以降に on になっているビットの添字を見つけてくれるやつです. これも文書化されてないようなので,...

や,文書化されているかどうかがそこまで大事かは人によりそうですけど. 消えたり仕様が変わったりしないのかなとか,えびちゃん的には気になっちゃう.

_GLIBCXX_DEBUG

デバッグに役立ちそうなやつ.

#define _GLIBCXX_DEBUG
#include <vector>

int main() {
  std::vector<int> v(2);
  v[100];  // error
}

gcc.gnu.org

文書化されてると安心しちゃう.

_USE_MATH_DEFINES

qnighy.hatenablog.com

ここが詳しい.

要するに,これを #define した場合の動作は未定義なので,我々が勝手に使えるはずの M_PI などの名前を処理系が勝手に提供してもコンパイラは怒られないという話.

トロール

処理系のコードをコピペしてきたのか知らないですけど,こういうテンプレを書いていませんか?

template <typename _Tp>
ostream& operator <<(ostream& os, _Tp const& x) {
  ...
  return os;
}

_Tp は取り締まり対象なんですよね.

まとめ

あえて予約された名前を宣言するメリットはないはずなので,避けませんかという話でした. それが原因で意味不明なエラーと戦いたい人は,すきにしたらいいと思います...?

処理系の独自機能であるようなものは,大抵は名前でわかる(_Exit など例外あり)ので,使う際にはどの程度の可搬性があるか考えてみるとよさそう?