C と C++ (これは継承されました) には、2 つの別個の識別子スペースがあります。1 つは変数、関数などの一般的なシンボル用で、もう 1 つはユーザー定義型 (列挙型、構造体) 用です。式は、一般的な識別子空間内のユーザー定義型へのtypedef
エイリアスを作成します。
識別子スペースは別々に保持されるため、この言語では、同じコンテキストでも同じ名前の関数と型を定義できます。
enum foo { no, yes };
void foo() {}
C では、ユーザー定義型空間の識別子を明示的に修飾する必要があったため、前の例でfoo
型を使用するには、次のようにする必要がありますenum foo
。
void foo( enum foo ) {}
ここで、 atypedef
はグローバル識別子空間にエイリアスを作成するため、typedef を使用することで、修飾なしで回避できます。
typedef enum bar { no, yes } bar;
void foo( bar ) {} // uses the typedef
時間をかけて C++ [*]に移行すると、両方の識別子スペースはまだ言語にあり、変更されたのはルックアップ ルールです。グローバル識別子空間で識別子を検索する場合 (つまり、前enum
に ,struct
またはclass
キーワードがなく、型以外が許可されているコンテキストで)、コンパイラは最も内側のスコープから外側のスコープまで、各スコープに対して検索を開始します。最初にグローバル識別子スペースを検索し、そこに何も見つからない場合は、ユーザー定義型も検索します。これがenum
、struct
またはをclass
オプションにするものです。ほとんどの場合、そうです。
あなたの特定のケースでは、結果を驚くべきものにすることができるいくつかのことを行っています。まず、同じ識別子を使用して 2 つの異なるものを参照する宣言があります。関数の名前に到達するまで、呼び出される唯一のエンティティfoo
はenum
:
foo // No need for 'enum', at this point function 'foo' is not declared
foo(); // No collision, this is 'foo' in global id space, not 'enum foo'
ここで、を単独でBar::foo
使用foo
すると、関数のスコープ内で検索が開始されますが、そこでは何も見つかりません。その後、Bar
クラス スコープに移動し、そこで関数があることがわかりfoo
、ルックアップがそこで停止します。クラスを離れる前にルックアップが停止するため、列挙子の typedef があるかどうかに関係なく、Bar
名前空間レベルでいずれかの識別子が見つかる可能性があることに注意してください。
ご指摘のとおり、enum
キーワードを追加すると、突然ルールが変更されます。現在、ユーザー定義型を探しています。検索は同じスコープに従いますが、ユーザー定義型の識別子スペースのみを検索します。したがって、 が存在Bar::foo
しない から に移動するため、名前空間レベルで列挙が見つかるまで外側に進みます。Bar
enum foo
名前空間修飾を指定すると、同じことが起こります。先頭に を付けるには、グローバル名前空間レベルで検索を開始する必要がありますfoo
。::
そのレベルでは、foo
定義されているのはユーザー定義型だけenum foo
です。enum
これは、キーワードを追加するかどうかと直交することに注意してください。foo
元のコードでは、名前空間レベルで関数を使用でき、同様の問題が発生します。
enum foo { no, yes };
void foo() {}
class Bar {
foo foo() { // Error [1]
return static_cast<::foo>(0); // Error [2]
}
};
この例では、2 つのエラーがあります。戻り値の型を宣言するとき、関数Bar::foo
はまだ宣言されていませんが、 があり::foo()
ます。上記のルールに従うと、ルックアップはグローバル名前空間まで外側に進み、 を::foo()
チェックする前に検索しますenum foo
。基本的に同じである 2 番目のエラーはstatic_cast
、グローバル名前空間を要求する際の修飾がここでは役に立たないことです。その理由は、ルックアップが を見つける::foo()
前に再び見つけてしまうからenum foo
です。このための正しいコードは次のようになります。
class Bar {
enum foo foo() {
return static_cast<enum ::foo>(0); // :: is optional!
}
};
foo
この場合、名前空間修飾は再びオプションですが、ルックアップによって検出されるユーザー定義型がないためです。しかし、コードをもう少し複雑にすることはできます...
enum foo { no, yes };
void foo() {}
class Bar {
struct foo {};
enum ::foo foo() {
return static_cast<enum ::foo>(0);
}
};
::
修飾がないと、 が見つかる前にヒットするユーザー定義型があるため、宣言と の両方が失敗しBar::foo()
ますstatic_cast
。がないと、コードもコンパイルに失敗します。これは、グローバル関数が.Bar::foo
enum ::foo
enum
::foo()
enum
要約すると、長い説明の後:しないでください。タイプと同じ名前の関数を作成しないでください。おそらく混乱を招くだけです。
[*]標準を読み直した後、C++ 言語はユーザー定義型の個別の識別子スペースを定義しません。一方、標準のルールは上記の説明と一致しています。主な違いは、C++ では、typedef
エイリアスをエイリアスする型以外の既存の型と同じにすることはできないということです。
struct foo {};
typedef int foo; // Error
しかし、他のほとんどの目的では、動作は一貫しています。回答の本文で言及しなかったいくつかのコーナーケースがありますが、それはこの質問の範囲をはるかに超えています。