1

g++との両方で次のコードを試しましたclang++。どちらもの本体内で型fooと関数名を区別できません。これはなぜですか?C++ 標準はこれを義務付けていますか? コンパイラは少なくとも両方を試してはいけませんか?foofoo

enum foo {
    FOO = 0,
    BAR,
    BAZ
};

class Bar {
    public:

    foo foo () const
    {
        // does not compile if I write static_cast<foo>(...)
        return static_cast< ::foo>(m_bar);
    }

    int m_bar;
};

int main ()
{
    Bar bar;
    bar.m_bar = 0;
    foo foo_bar = bar.foo(); 
    return 0;
}

に置き換えることができ::fooenum foo問題なくコンパイルされます。ただし、に変更enum foo {...}するtypedef enum _foo {...} fooと、同じ問題が残ります ( http://ideone.com/d1GiO )。

4

2 に答える 2

4

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キーワードがなく、型以外が許可されているコンテキストで)、コンパイラは最も内側のスコープから外側のスコープまで、各スコープに対して検索を開始します。最初にグローバル識別子スペースを検索し、そこに何も見つからない場合は、ユーザー定義型も検索します。これがenumstructまたはをclassオプションにするものです。ほとんどの場合、そうです。

あなたの特定のケースでは、結果を驚くべきものにすることができるいくつかのことを行っています。まず、同じ識別子を使用して 2 つの異なるものを参照する宣言があります。関数の名前に到達するまで、呼び出される唯一のエンティティfooenum:

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しない から に移動するため、名前空間レベルで列挙が見つかるまで外側に進みます。Barenum 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::fooenum ::fooenum::foo()enum

要約すると、長い説明の後:しないでください。タイプと同じ名前の関数を作成しないでください。おそらく混乱を招くだけです。


[*]標準を読み直した後、C++ 言語はユーザー定義型の個別の識別子スペースを定義しません。一方、標準のルールは上記の説明と一致しています。主な違いは、C++ では、typedefエイリアスをエイリアスする型以外の既存の型と同じにすることはできないということです。

struct foo {};
typedef int foo; // Error

しかし、他のほとんどの目的では、動作は一貫しています。回答の本文で言及しなかったいくつかのコーナーケースがありますが、それはこの質問の範囲をはるかに超えています。

于 2012-08-20T22:31:01.260 に答える
1

明示的にスコープを指定しない限り、ネームスペースは in to out です。だからあなたのstatic_cast<foo>(...)手段static_cast<first foo found>(...)。これは C の場合も同様で、おそらく言語自体の特性です。すべての名前は完全に修飾されているため、最初にヒットしfooたものだけが重要です。

于 2012-08-20T20:37:59.330 に答える