web-dev-qa-db-ja.com

演算子の優先順位以外に、余分な括弧が有効になるのはいつですか?

C++の括弧は多くの場所で使用されています。関数呼び出しおよびグループ化式で、演算子の優先順位をオーバーライドします。 不正な余分な括弧を除いて(関数呼び出し引数リストの周りなど)、C++の一般的な-絶対ではない規則は、余分な括弧は決して傷つけない

5.1プライマリ式[expr.prim]

5.1.1一般[expr.prim.general]

6括弧で囲まれた式は、その式と値が囲まれた式のものと同一のプライマリ式です。括弧が存在しても、式が左辺値であるかどうかには影響しません。括弧で囲まれた式は、囲まれた式を使用できるコンテキストとまったく同じコンテキストで使用でき、同じ意味で特に指定のない限りです。

質問:基本的な演算子の優先順位をオーバーライドする以外に、コンテキストで余分な括弧を使用すると、C++プログラムの意味が変わりますか?

[〜#〜] note [〜#〜]pointer-to-member構文を、括弧のない&qualified-idへの制限がスコープ外にあると考えていますなぜなら、意味が異なる2つの構文を許可するのではなく、 構文を制限する であるためです。同様に、プリプロセッサマクロ定義内の括弧を使用すると、不要な演算子の優先順位も保護されます。

90
TemplateRex

TL; DR

余分な括弧は、次のコンテキストでC++プログラムの意味を変更します。

  • 引数依存の名前検索を防止する
  • リストコンテキストでコンマ演算子を有効にする
  • 厄介な解析のあいまいさの解決
  • decltype式の参照性の推定
  • プリプロセッサマクロエラーの防止

引数に依存する名前検索の防止

標準の付録Aで詳しく説明されているように、フォーム_post-fix expression_の_(expression)_は_primary expression_ですが、_id-expression_ではなく、したがって_unqualified-id_ではありません。これは、従来の形式fun(arg)と比較して、_(fun)(arg)_の形式の関数呼び出しでは、引数に依存する名前検索が防止されることを意味します。

3.4.2引数依存の名前検索[basic.lookup.argdep]

1関数呼び出し(5.2.2)のpostfix-expressionがunqualified-idである場合、通常の非修飾ルックアップ(3.4 .1)が検索され、それらの名前空間で、名前空間スコープのフレンド関数または関数テンプレート宣言(11.3)が見られない場合があります。検索に対するこれらの変更は、引数のタイプ(およびテンプレートテンプレート引数の場合、テンプレート引数の名前空間)に依存します。 [例:

_namespace N {
    struct S { };
    void f(S);
}

void g() {
    N::S s;
    f(s);   // OK: calls N::f
    (f)(s); // error: N::f not considered; parentheses
            // prevent argument-dependent lookup
}
_

—例の終了]

リストコンテキストでのコンマ演算子の有効化

コンマ演算子は、ほとんどのリストのようなコンテキスト(関数とテンプレートの引数、初期化リストなど)で特別な意味を持ちます。このようなコンテキストでのa, (b, c), d形式の括弧は、コンマ演算子が適用されない通常のフォーム_a, b, c, d_と比較して、コンマ演算子を有効にできます。

5.18コンマ演算子[expr.comma]

2コンマに特別な意味が与えられているコンテキストでは、[例:関数の引数のリスト(5.2.2)および初期化子のリスト(8.5) —最後の例]節5で説明されているコンマ演算子は、括弧内でのみ使用できます。 [例:

_f(a, (t=3, t+2), c);
_

3つの引数があり、2番目の引数の値は5です。

厄介な解析のあいまいさの解決

Cおよびその難解な関数宣言構文との下位互換性により、厄介な解析と呼ばれる驚くべき解析のあいまいさが生じる可能性があります。基本的に、宣言として解析できるものはすべて、競合する解析も適用されますが、oneとして解析されます。

6.8あいまいさの解決[stmt.ambig]

1式ステートメントと宣言を含む文法にはあいまいさがあります:関数スタイルの明示的な型変換を伴う式ステートメント(5.2.3)左端の部分式は、最初の宣言子が(。で始まる宣言と区別できないため、これらの場合、ステートメントは宣言です。

8.2あいまいさの解決[dcl.ambig.res]

16.8で述べた関数スタイルのキャストと宣言の類似性から生じるあいまいさは、宣言のコンテキストでも発生する可能性があります。そのコンテキストでは、パラメーター名を囲む括弧の冗長セットを持つ関数宣言と、初期化子として関数スタイルのキャストを持つオブジェクト宣言のどちらかを選択します。 6.8で述べたあいまいさの場合と同様に、解決策は宣言または宣言である可能性のある構造を考慮することです。 [注:宣言は、非関数スタイルのキャスト、初期化を示す=によって、またはパラメーター名の周りの冗長な括弧を削除することによって、明確に明確にすることができます。 —注を終了] [例:

_struct S {
    S(int);
};

void foo(double a) {
    S w(int(a));  // function declaration
    S x(int());   // function declaration
    S y((int)a);  // object declaration
    S z = int(a); // object declaration
}
_

—例の終了]

これの有名な例は、 Most Vexing Parse 、アイテム6でスコット・マイヤーズによって普及した名前です彼の 効果的なSTL 本:

_ifstream dataFile("ints.dat");
list<int> data(istream_iterator<int>(dataFile), // warning! this doesn't do
               istream_iterator<int>());        // what you think it does
_

これは、戻り型が_list<int>_である関数dataを宣言します。関数データは2つのパラメーターを取ります。

  • 最初のパラメーターの名前はdataFileです。タイプは_istream_iterator<int>_です。 dataFileを囲む括弧は不要であり、無視されます。
  • 2番目のパラメーターには名前がありません。その型は、何も取らず_istream_iterator<int>_を返す関数へのポインターです。

最初の関数引数の周りに余分な括弧を配置すると(2番目の引数の周りの括弧は無効です)、あいまいさが解消されます。

_list<int> data((istream_iterator<int>(dataFile)), // note new parens
                istream_iterator<int>());          // around first argument
                                                  // to list's constructor
_

C++ 11には、多くのコンテキストでこのような構文解析の問題を回避するためのブレース初期化構文があります。

decltype式の参照性の推定

auto型の推論とは対照的に、decltypeでは、参照性(左辺値と右辺値の参照)を推定できます。ルールはdecltype(e)decltype((e))の式を区別します:

7.1.6.2単純型指定子[dcl.type.simple]

4式eの場合、decltype(e)で示される型は次のように定義されます。

eが括弧なしのid-expressionまたは括弧なしのクラスメンバーアクセス(5.2.5)の場合、decltype(e)eで指定されたエンティティのタイプです。そのようなエンティティが存在しない場合、またはeがオーバーロードされた関数のセットに名前を付けている場合、プログラムは不正な形式です。

—それ以外の場合、eがxvalueの場合、decltype(e)は_T&&_です。ここで、Teのタイプです。

—それ以外の場合、eが左辺値である場合、decltype(e)は_T&_です。ここで、Teのタイプです。

—それ以外の場合、decltype(e)eのタイプです。

Decltype指定子のオペランドは、未評価のオペランドです(5節)。 [例:

_const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = 0;   // type is const int&&
decltype(i) x2;           // type is int
decltype(a->x) x3;        // type is double
decltype((a->x)) x4 = x3; // type is const double&
_

—例の終了] [注:decltype(auto)を含む型を決定するための規則は、7.1.6.4で指定されています。 —注を終了]

decltype(auto)のルールは、初期化式のRHS内の追加の括弧に対して同様の意味を持ちます。 C++ FAQ および これに関連するQ&A

_decltype(auto) look_up_a_string_1() { auto str = lookup1(); return str; }  //A
decltype(auto) look_up_a_string_2() { auto str = lookup1(); return(str); } //B
_

最初はstringを返し、2番目は_string &_を返します。これはローカル変数strへの参照です。

プリプロセッサマクロ関連エラーの防止

適切なC++言語との相互作用には、プリプロセッサマクロに多くの微妙な点があります。最も一般的なものを以下に示します。

  • 不要な演算子の優先順位を回避するために、マクロ定義#define TIMES(A, B) (A) * (B);内のマクロパラメーターを囲む括弧を使用します(たとえば、TIMES(1 + 2, 2 + 1)では、9を生成しますが、_(A)_を囲む括弧なしで6を生成します。 _(B)_
  • 内部にコンマが含まれるマクロ引数の周りに括弧を使用する:assert((std::is_same<int, int>::value));
  • 関数を囲む括弧を使用して、インクルードヘッダーのマクロ展開から保護します:_(min)(a, b)_(ADLも無効にするという望ましくない副作用があります)
109
TemplateRex

一般に、プログラミング言語では、「余分な」括弧は、構文解析の順序または意味を変更するnotであることを意味します。コードを読んでいる人の利益のために順序(演算子の優先順位)を明確にするために追加されており、それらの唯一の効果は、コンパイルプロセスをわずかに遅くし、コードを理解する際の人為的エラーを減らすことです(おそらく全体的な開発プロセスをスピードアップします) )。

括弧のセットが実際にが式の解析方法を変更する場合、それらは定義によりnot extraです。違法/無効な解析を有効な解析に変換する括弧は「余分」ではありませんが、そのmayは不適切な言語設計を示しています。

4
Phil Perry