10

次の C++11 プログラムは不正ですか?

struct a
{
    struct b {  };

    void f() {};
};

extern struct a b;

struct a ::b;

int main()
{
    b.f();
}

なぜ/なぜではないのですか?

ここで興味深いのは、次の行です。

struct a ::b;

これは内部クラスの前方宣言a::bですか?

それとも、これはグローバル変数の定義bですか? に相当:

struct a (::b);
4

2 に答える 2

-3

テイク 2

struct a ::b;の整形式宣言ですstruct a::b

は既に定義されているため、前方宣言ではありません。標準は、前方宣言struct a::bを正式に定義していませんが、その定義に先行し、その定義から分離されている何かの宣言を意味することは広く受け入れられています。

グローバル変数の宣言でもありませんb

空白は重要ではないため、推定上の宣言に示唆された曖昧さを持たせるために、

/*A*/ struct a::b;

もちろん、同じあいまいさを持たなければなりません。このように書き直すと、宣言が何を意味するかは完全に明白であることに同意するかもしれません。しかし、その明白な意味が標準によって立証されているかどうかを知りたい.

私の考察は C++11 標準に限定します。(C++03標準からの質問の解決は、実際にはより簡単だと思います)。

標準では、単項スコープ演算子 ::スコープ解決演算 子と区別してい::ます。

§ 3.4.3修飾名検索、パラ 1:

クラスまたは名前空間のメンバーまたは列挙子の名前は、そのクラス、名前空間、または列挙を示す入れ子になった名前指定子に :: スコープ解決演算子 (5.1) を適用した後に参照できます。

§ 3.4.3修飾名検索、パラ 4:

単項スコープ演算子 :: (5.1) がプレフィックスとして付けられた名前は、それが使用される翻訳単位で、グローバル スコープで検索されます。

これらの引用は、違いを説明するためではなく、存在することを示すためのものです。しかし、それらは、演算子のグローバルな解釈を維持するために、§ 3.4.3/4の単項スコープ演算子/*A*/で なければならないという点をすぐに伝えます。::b/*A*/

のスコープ演算子が 単項演算子で/*A*/ないことは明らかです。しかし、単項スコープ演算子スコープ解決演算子の文法的定義については§ 5.1一次式を参照してください。いずれにせよ、 if がスコープの右側のオペランドである場合のルックアップについてはまだ説明されていません。解決演算子.b

§ 5.1.1/8 では、この::演算子は § 3.4.3/1 の中置演算子として定義されており、また § 3.4.3/4 の単項演算子としても、 qualified-idの文法で定義されていることがわかります。

qualified-id:
    nested-name-specifier template[opt] unqualified-id
   :: identifier
   :: operator-function-id
   :: literal-operator-id
   :: template-id
nested-name-specifier:
    ::[opt] type-name ::
    ::[opt] namespace-name ::
    decltype-specifier ::
    nested-name-specifier identifier ::
    nested-name-specifier template[opt] simple-template-id ::

これは、 nested-name-specifierの最後のトークンであり 、オプションtemplateで 、その後にunqualified-idが続く場合は中置演算子であり、それ以外の場合はコンテキスト内の単項演算子です。

:: identifier
:: operator-function-id
:: literal-operator-id
:: template-id

この文法によれば、可能であれば、演算子はネストされた名前指定子(スコープ解決演算子を形成する)::と左関連付けする必要があり ます。文法によると、inはネストされた名前指定 であり、最初の形式の ネストされた名前指定子 template[opt] 非修飾 ID の修飾 IDです。a::/*A*/a::b

したがって、§ 3.4.3修飾名のルックアップは に適用さb/*A*/、§ 3.4.3.1クラス メンバーのパラ 1を参照できることが保証さbれていますa

修飾 ID のネストされた名前指定子がクラスを指定する場合、ネストされた名前指定子の後に指定された名前は、以下にリストされている場合を除いて、クラス (10.2) のスコープで検索されます。

「以下に挙げるケース」はいずれも には適用されず、入れ子になったクラスが のメンバーとしてカウントされる/*A*/ことに疑いがある場合は、§ 9.2クラス メンバーの第 1 項でそれを削除します。a::ba

クラスのメンバーは、データ メンバー、メンバー関数 (9.3)、ネストされた型、および列挙子です。

を検索することがa::b明確に必要bであることがわかったのでa、 の整形式性の問題は、が標準に従った宣言/*A*/であるかどうかの問題になります (確かに同じ場所にあるように)。/*A*/struct a;

これは、 宣言-grammar:-のいくつかのステップを踏むことで確認できます。

  • § 7 Declarationsの第 1 項によると、宣言simple -declarationである場合があり、simple-declarationdecl-specifier-seqである場合があります。

  • § 7.1 Specifiersの第 1 項によると、decl-specifier-seqdecl-specifierである場合があり、decl-specifierはtype-specifierである場合があります。

  • § 7.1.6 Type Specifiersの第 1 項によると、type-specifierは、 精巧なtype-specifier である場合があります。

  • § 7.1.6.3 Elaborated type specifiersのパラ 0 によると、elaborated-type-specifier は次のようになります。

    class-key attribute-specifier-seq[opt] nested-name-specifier[opt] identifier

ここで、class-keyclassstructまたはunion(§ 9 Classesの第 1 項による) であり、オプションのattribute-specifier-seqは必要ないため重要ではありません。

/*A*/満足:

class-key nested-name-specifier identifier

ということで宣言です。

規格ごとに/*A*/、 の再宣言ですa::b

GCC と CLANG に関するいくつかのコメント

Clang 3.3 は、投稿されたコードを診断します。

error: forward declaration of struct cannot have a nested name specifier

宣言が前方ではないため、これは間違っています。問題のある宣言を移動して定義の前に置くとa::b、clang は不適切な形式の前方宣言をフォールトせず、代わりに次のように診断します。

error: use of undeclared identifier 'a'

struct a ::b;そして、プログラムをthenに置き換えた場合、clang はその行に障害を検出しないため、以前はその型が (違法に) 前方宣言されていたのと同じポイントで のstruct a ::b x;定義を見つけました 。a::b

GCC 4.8.1 の診断:

warning: declaration ‘struct a::b’ does not declare anything
undefined reference to `b'

警告は単なる警告であり、整形式の再宣言である宣言と一致していますが、「新しい」という単語を追加するとより役立つ場合があります。問題のある宣言を実際に前方に移動すると、GCC は当然、clang と同じエラーを返します。

2 番目のリンケージ エラーは、extern struct b で呼び出された undefined を参照しており、 によって定義mainが正しく提供されていませんstruct a ::b;

于 2013-07-30T22:51:49.637 に答える