IT박스

연산자 우선 순위 외에 추가 괄호가 언제 영향을 미칩니 까?

itboxs 2020. 9. 6. 09:09
반응형

연산자 우선 순위 외에 추가 괄호가 언제 영향을 미칩니 까?


C ++의 괄호는 여러 위치에서 사용됩니다. 예를 들어 연산자 우선 순위를 재정의하기위한 함수 호출 및 그룹화 표현식에서. 그렇다 불법 여분의 괄호에서 (예 : 주변에 함수 호출 인수 목록과 같은), C의 일반적인 - 그러나 absolute-하지 규칙 ++ 것입니다 추가 괄호 해치지 않을 :

5.1 기본 표현식 [expr.prim]

5.1.1 일반 [expr.prim.general]

6 괄호로 묶인 표현식은 유형과 값이 괄호 안에있는 표현식과 동일한 기본 표현식입니다. 괄호가 있어도 표현식이 lvalue인지 여부에는 영향을주지 않습니다. 괄호로 묶인 표현식은 괄호로 묶인 표현식을 사용할 수있는 경우와 정확히 동일한 컨텍스트에서 사용할 수 있으며 달리 표시된 경우를 제외하고 동일한 의미로 사용할 수 있습니다 .

질문 : 기본 연산자 우선 순위를 재정의하는 것 외에 추가 괄호가 C ++ 프로그램의 의미를 변경하는 컨텍스트는 무엇입니까?

참고 : 멤버에 대한 포인터 구문의 제한 은 다른 의미를 가진 두 개의 구문을 허용하는 대신 구문&qualified-id제한 하기 때문에 괄호없이 범위를 벗어나는 것으로 간주합니다 . 마찬가지로 전 처리기 매크로 정의 내 에서 괄호를 사용하면 원치 않는 연산자 우선 순위를 방지 할 수 있습니다.


TL; DR

추가 괄호는 다음 컨텍스트에서 C ++ 프로그램의 의미를 변경합니다.

  • 인수 종속 이름 조회 방지
  • 목록 컨텍스트에서 쉼표 연산자 활성화
  • 성가신 구문 분석의 모호성 해결
  • decltype표현의 참조 성 추론
  • 전 처리기 매크로 오류 방지

인수 종속 이름 조회 방지

표준의 부록 A에 자세히 설명 된대로 post-fix expression형식의 (expression)a 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);

세 개의 인수가 있으며 두 번째 인수의 값은 5입니다. —end example]

성가신 구문 분석의 모호성 해결

C 및 그 신비한 함수 선언 구문과의 역 호환성은 성가신 구문 분석으로 알려진 놀라운 구문 분석 모호성을 초래할 수 있습니다. 본질적으로, 선언으로 파싱 할 수있는 모든 것은 경쟁 파싱도 적용 되더라도 하나로 파싱됩니다.

6.8 모호성 해결 [stmt.ambig]

1 There is an ambiguity in the grammar involving expression-statements and declarations: An expression-statement with a function-style explicit type conversion (5.2.3) as its leftmost subexpression can be indistinguishable from a declaration where the first declarator starts with a (. In those cases the statement is a declaration.

8.2 Ambiguity resolution [dcl.ambig.res]

1 The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in 6.8 can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. Just as for the ambiguities mentioned in 6.8, the resolution is to consider any construct that could possibly be a declaration a declaration. [ Note: A declaration can be explicitly disambiguated by a nonfunction-style cast, by an = to indicate initialization or by removing the redundant parentheses around the parameter name. —end note ] [ Example:

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
}

—end example ]

A famous example of this is the Most Vexing Parse, a name popularized by Scott Meyers in Item 6 of his Effective STL book:

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

This declares a function, data, whose return type is list<int>. The function data takes two parameters:

  • The first parameter is named dataFile. It's type is istream_iterator<int>. The parentheses around dataFile are superfluous and are ignored.
  • The second parameter has no name. Its type is pointer to function taking nothing and returning an istream_iterator<int>.

Placing extra parentheses around the first function argument (parentheses around the second argument are illegal) will resolve the ambiguity

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

C++11 has brace-initializer syntax that allows to side-step such parsing problems in many contexts.

Deducing referenceness in decltype expressions

In contrast to auto type deduction, decltype allows referenceness (lvalue and rvalue references) to be deduced. The rules distinguish between decltype(e) and decltype((e)) expressions:

7.1.6.2 Simple type specifiers [dcl.type.simple]

4 For an expression e, the type denoted by decltype(e) is defined as follows:

— if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;

— otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;

— otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;

— otherwise, decltype(e) is the type of e.

The operand of the decltype specifier is an unevaluated operand (Clause 5). [ Example:

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&

—end example ] [ Note: The rules for determining types involving decltype(auto) are specified in 7.1.6.4. —end note ]

The rules for decltype(auto) have a similar meaning for extra parentheses in the RHS of the initializing expression. Here's an example from the C++FAQ and this related 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

The first returns string, the second returns string &, which is a reference to the local variable str.

Preventing preprocessor macro related errors

There is a host of subtleties with preprocessor macros in their interaction with the C++ language proper, the most common of which are listed below

  • using parentheses around macro parameters inside the macro definition #define TIMES(A, B) (A) * (B); in order to avoid unwanted operator precedence (e.g. in TIMES(1 + 2, 2 + 1) which yields 9 but would yield 6 without the parentheses around (A) and (B)
  • using parentheses around macro arguments having commas inside: assert((std::is_same<int, int>::value)); which would otherwise not compile
  • using parentheses around a function to protect against macro expansion in included headers: (min)(a, b) (with the unwanted side effect of also disabling ADL)

In general, in programming languages, "extra" parentheses implies that they are not changing the syntactical parsing order or meaning. They are being added to clarify the order (operator precedence) for the benefit of people reading the code, and their only effect would be to slightly slow the compile process, and reduce human errors in understanding the code (probably speeding up the overall development process).

If a set of parentheses actually changes the way an expression is parsed, then they are by definition not extra. Parentheses that turn an illegal/invalid parse into a legal one are not "extra", although that may point out a poor language design.

참고URL : https://stackoverflow.com/questions/24116817/when-do-extra-parentheses-have-an-effect-other-than-on-operator-precedence

반응형