1

たとえば、前方宣言と代替関数構文に関するウィキペディアのセクションから:

Ret 型は、Lhs 型と Rhs 型の追加によって生成されるものです。[...] decltype を使用しても、これは不可能です。

template<class Lhs, class Rhs>
decltype(lhs+rhs)  //Not legal C++11
      adding_func(const Lhs &lhs, const Rhs &rhs) {
  return lhs + rhs;
}

lhs と rhs がまだ定義されていないため、これは正当な C++ ではありません。パーサーが残りの関数プロトタイプを解析するまで、それらは有効な識別子にはなりません。

システムが大きなメモリ プレッシャーにさらされていたとき、これは理解できました (これにより、パスをストリーミング操作として実行できました)。しかし、なぜこの時代 (私のスマートフォンには、完全なソース コード、完全な解析ツリー、そして適切なファイル用のいくつかを保持するのに十分な RAM がある) で、トークンの順序が重要になるのでしょうか? decltypeたとえば、その中の識別子がタイプまたは変数であるかどうかを知らない限り、非端末生成の終わりを見つけることが不可能になる文法の奇妙なコーナーケースはありますか? もう 1 つのポイントとして、グローバル/名前空間スコープの宣言の順序がまったく問題になるのはなぜですか?


編集、これは合法であることがわかりました:

class foo {};
foo Foober(int foo) { return ::foo(); }

私が実際にこれに遭遇した場合、それは The Daily WTF に掲載されることになりますが、それでも合法です。

4

4 に答える 4

4

「これは正当な C++ ではありません」という記述は正確ではありません。関数宣言が表示されるスコープで C++ が定義されlhsていれば、合法です。rhsパラメーターlhsrhsは外側のスコープにはありませんが、名前が他の宣言のスコープ内にある可能性は確かにあります。関数の戻り値の型のスコープを変更すると、以前は有効だったプログラムの意味が暗黙のうちに変更される可能性があります。

C++ では、スコープは常に字句順であるとは限りません。たとえば、メンバー関数の本体内では、まだ宣言されていないものも含め、すべてのメンバーが表示されます。(先頭の戻り値の型もメンバー関数本体の一部ではありませんが、末尾の戻り値は含まれています。)

たとえば、識別子がタイプまたは変数であるかどうかがわからない限り、decltype の非端末生成の終わりを見つけることが不可能になる文法の奇妙なケースはありますか?

私はそうは思わない; の引数を囲む括弧はdecltype明確にする必要があります。しかし、識別子がテンプレートであるかどうかがわからない場合、それは<>および>>トークンの意味に、非常に微妙な方法で影響を与える可能性があります。また、識別子が型なのか関数なのかがわからないことも、式の解析に困難をもたらします (C であっても)。そのため、名前の「種類」が重要なコーナー ケースが確実に存在します。

于 2013-01-17T21:51:57.287 に答える
4

これは容量の問題ではなく、下位互換性の問題です。

言語の新しいバージョンは、下位互換性を維持するために、事実上すべての古い機能を保持する必要があります。元の C++ では、メンバー関数の戻り値の型は、クラスまたは関数のスコープで検索されませんでした。関数宣言の囲みスコープで検索されました。このルールを変更すると、下位互換性が損なわれる可能性があります。

decltypeこれを回避する唯一の方法は、引数専用の例外を作成することです。しかし、これは非常に一貫性がなく、エラーが発生しやすくなります。

于 2013-01-17T22:27:34.000 に答える
1

彼らはどこかで「内側」と「外側」のスコープの間に線を引く必要があります。おっしゃったように、プロダクション内の識別子が解決できると、プロダクションの終わりを見つけるのが簡単になります。コンパイラで実装する方が簡単で、言語標準で一貫して指定する方が簡単です。それがおそらく、最初のコンパイラがそのようにした理由であり、テンプレート展開で未定義の (ネストされた) 識別子によって引き起こされる構文のあいまいさを解決するために、言語が常にtypenameandキーワードを必要とする理由です。template

また、宣言に現れる前に識別子を使用することはできません。これは、人間または機械のリーダーが関連する宣言を見つけるのに役立つ素晴らしい規則です。(利便性を向上させるために特別な 2 パス解析が適用されるクラス メンバー間を除きます。)

1 つの方法で完了すると、コミットメントが作成され、言語を変更することはできません。機能を追加することを除いて、つまり、それが C++11 によって問題が解決された方法です。

template<class Lhs, class Rhs>
  auto adding_func(const Lhs &lhs, const Rhs &rhs) -> decltype(lhs+rhs)
    {return lhs + rhs;}

ちなみに、彼らが言及していない別の回避策があります:

template<class Lhs, class Rhs>
  decltype( declval< Lhs >() + declval< Rhs >() )
    adding_func(const Lhs &lhs, const Rhs &rhs) {return lhs + rhs;}
于 2013-01-17T22:29:29.620 に答える
1

あなたの前提は、前方宣言は純粋にストリーム解析を可能にするためにメモリを節約するためのものであり(ソースファイル全体をメモリに保持するのではなく)、実際には正しくないということです。

私たちが使用する解析アルゴリズムの多くは、フォワード パスの作成に基づいており、バックトラックが必要な場合は、曖昧さを解消するために解析時間 (または空間) が指数関数的に爆発する可能性があります。

たとえば、それが何であるかを何も知らずに、宣言される前に一時的に識別子を受け入れることは、それが何であるかのすべての可能性を一時的に説明する解析を続行する必要があることを意味します。(誤字脱字含む)

于 2013-01-18T11:55:49.187 に答える