값 초기화에 대한 나의 시도는 함수 선언으로 해석되며 왜 A a (()); 해결?


158

Stack Overflow가 가르쳐 준 많은 것들 중에는 "가장 vexing parse"라고 알려진 것이 있습니다.

A a(B()); //declares a function

이것은 대부분 직관적 으로 생성자 매개 변수로 임시 객체를 사용하여 a유형 의 객체 를 선언하는 것처럼 보이지만 실제로는 매개 변수 를 반환 하지 않고 반환하는 함수에 대한 포인터를 가져 와서 실제로는을 반환하는 함수의 선언입니다. . 마찬가지로 선ABaAB

A a(); //declares a function

객체 대신 함수를 선언하기 때문에 같은 범주에 속합니다. 이제 첫 번째 경우이 문제에 대한 일반적인 해결 방법 B()은 컴파일러가 객체 선언으로 해석하므로 괄호 / 괄호를 추가하는 것입니다.

A a((B())); //declares an object

그러나 두 번째 경우 동일한 작업을 수행하면 컴파일 오류가 발생합니다.

A a(()); //compile error

내 질문은 왜? 예, 올바른 '해결 방법'이로 변경하는 것임을 잘 알고 A a;있지만 ()첫 번째 예제에서 컴파일러 의 추가 기능 이 다시 적용 할 때 작동하지 않는 것이 무엇인지 알고 싶습니다 . 두 번째 예. 는 IS A a((B()));해결 방법은 표준에 기록 특정 예외?


20
(B())C ++ 표현식 일뿐입니다. 예외는 아닙니다. 유일한 차이점은 유형으로 구문 분석 될 수있는 방법이 없다는 것입니다.
Pavel Minaev

12
또한 두 번째 경우 는 동일한 범주 A a();아닙니다 . 를 들어 컴파일러 , 그것을 구문 분석 할 수있는 다른 방법이 결코이 항상 함수 선언 그래서 그 장소에서 이니셜은 결코 빈 괄호로 구성합니다.
Johannes Schaub-litb

11
litb의 우수한 점은 미묘하지만 중요한 점이며 강조 할 가치가 있습니다.이 선언에 모호성이 존재하는 이유 'A a (B ())'가 'B ()'의 구문 분석에 있습니다-> 둘 다 표현식 일 수 있습니다 & 선언 및 컴파일러는 expr보다 decl을 'pick'해야합니다. 따라서 B ()가 decl이면 'a'는 func decl (변수 decl이 아닌) 일 수 있습니다. '()'가 초기화 자로 허용되면 'A a ()'는 모호하지만 expr vs decl은 아니지만 var decl vs func decl은 하나의 decl을 선호하는 규칙이 없으므로 '() '는 이니셜 라이저로 허용되지 않으며 모호성이 증가하지 않습니다.
파이살 발리

6
A a();있다 없다 의 예를 들어 가장 성가신 구문 분석 . C 에서처럼 함수 선언 일뿐입니다.
Pete Becker

2
"올바른 '해결 방법'은 A a;" 으로 변경하는 것이 잘못되었습니다. POD 유형의 초기화는 제공하지 않습니다. intitialization write를 얻으려면 A a{};.
건배와 hth. -Alf

답변:


70

깨달은 대답은 없습니다. C ++ 언어로 유효한 구문으로 정의되지 않았기 때문입니다. 따라서 언어의 정의에 따라 결정됩니다.

안에식이 있으면 유효합니다. 예를 들면 다음과 같습니다.

 ((0));//compiles

더 간단한 설명 : (x)유효한 C ++식이 기 때문에 ()그렇지 않습니다.

언어가 정의되는 방식과 컴파일러의 작동 방식에 대해 자세히 알아 보려면 공식 언어 이론 또는보다 구체적으로 문맥 자유 문법 (CFG) 및 유한 상태 기계와 같은 관련 자료에 대해 학습해야 합니다. wikipedia 페이지로는 충분하지 않지만 책에 대한 정보가 필요합니다.


45
더 간단한 설명 : (x)유효한 C ++식이 기 때문에 ()그렇지 않습니다.
Pavel Minaev

이 답변을 수락했습니다. 또한 초기 질문에 대한 Pavel의 의견이 많은 도움이되었습니다
GRB


29

C 함수 선언자

우선, C가 ​​있습니다. C에서는 A a()함수 선언입니다. 예를 들어 putchar다음과 같은 선언이 있습니다. 일반적으로 이러한 선언은 헤더 파일에 저장되지만 함수 선언이 어떻게 보이는지 알면 수동으로 작성하는 것을 막을 수있는 것은 없습니다. 인수 이름은 선언에서 선택 사항이므로이 예제에서는 생략했습니다.

int putchar(int);

이를 통해 이와 같은 코드를 작성할 수 있습니다.

int puts(const char *);
int main() {
    puts("Hello, world!");
}

C를 사용하면 함수 호출처럼 보이는 읽기 쉬운 구문으로 함수를 인수로 취하는 함수를 정의 할 수 있습니다 (함수에 대한 포인터를 반환하지 않는 한 읽을 수 있습니다).

#include <stdio.h>

int eighty_four() {
    return 84;
}

int output_result(int callback()) {
    printf("Returned: %d\n", callback());
    return 0;
}

int main() {
    return output_result(eighty_four);
}

앞서 언급했듯이 C는 헤더 파일에서 인수 이름을 생략 할 수 있으므로 헤더 파일에서 output_result이와 같이 보입니다.

int output_result(int());

생성자에서 하나의 인수

당신은 그것을 인식하지 못합니까? 글쎄, 내가 당신을 생각 나게합니다.

A a(B());

네, 정확히 같은 함수 선언입니다. Ais int, ais output_resultBis int입니다.

C의 새로운 기능과 C의 충돌을 쉽게 알 수 있습니다. 정확히 말하자면 생성자는 클래스 이름과 괄호이며 ()대신을 사용 하는 대체 선언 구문 입니다 =. 의도적으로 C ++은 C 코드와 호환되도록 노력하므로 실제로는 아무도 신경 쓰지 않아도이 경우를 처리해야합니다. 따라서 이전 C 기능은 새로운 C ++ 기능보다 우선합니다. 선언 문법은 ()실패하면 새 구문으로 되돌리기 전에 이름을 함수로 일치시킵니다 .

이러한 기능 중 하나가 존재하지 않거나 다른 구문 ( {}C ++ 11 과 같이 )이있는 경우이 문제는 인수가 하나 인 구문에 대해서는 발생하지 않았을 것입니다.

이제 왜 A a((B()))작동 하는지 물어볼 수 있습니다. output_result쓸모없는 괄호로 선언하자 .

int output_result((int()));

작동하지 않습니다. 문법에서는 변수가 괄호 안에 있지 않아야합니다.

<stdin>:1:19: error: expected declaration specifiers or ‘...’ before ‘(’ token

그러나 C ++은 표준 표현을 기대합니다. C ++에서는 다음 코드를 작성할 수 있습니다.

int value = int();

그리고 다음 코드는.

int value = ((((int()))));

C ++은 괄호 안의식이 C 형과 달리식이 될 것으로 기대합니다. 괄호는 여기서 의미가 없습니다. 그러나 쓸모없는 괄호를 삽입하면 C 함수 선언이 일치하지 않고 새 구문을 올바르게 일치시킬 수 있습니다 (예 :와 같은 표현식이 필요함 2 + 2).

생성자에서 더 많은 인수

분명히 하나의 주장은 좋지만 두 가지는 어떻습니까? 생성자가 인수를 하나만 가질 수있는 것은 아닙니다. 두 개의 인수를 취하는 내장 클래스 중 하나는std::string

std::string hundred_dots(100, '.');

이것은 모두 훌륭하고 훌륭합니다 (기술적으로,로 작성된다면 대부분의 vexing 구문 분석이 필요합니다 std::string wat(int(), char()). 그러나 솔직히 말하십시오-누가 그것을 쓸까요 ?하지만이 코드에 vexing 문제가 있다고 가정합시다. 괄호 안의 모든 것.

std::string hundred_dots((100, '.'));

그렇지 않습니다.

<stdin>:2:36: error: invalid conversion from char to const char*’ [-fpermissive]
In file included from /usr/include/c++/4.8/string:53:0,
                 from <stdin>:1:
/usr/include/c++/4.8/bits/basic_string.tcc:212:5: error:   initializing argument 1 of std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ [-fpermissive]
     basic_string<_CharT, _Traits, _Alloc>::
     ^

g ++ 시도 변환하는 이유는 확실하지 않다 charconst char *. 어느 쪽이든, 생성자는 하나의 값 유형으로 만 호출되었습니다 char. type의 인수가 하나 인 오버로드가 없으므로 char컴파일러가 혼동됩니다. 당신은 물어볼 수 있습니다-왜 인자가 char 타입입니까?

(100, '.')

예, ,여기에 쉼표 연산자가 있습니다. 쉼표 연산자는 두 개의 인수를 사용하여 오른쪽 인수를 제공합니다. 그것은 실제로 유용하지는 않지만 내 설명으로 유명합니다.

대신 가장 까다로운 구문 분석을 해결하려면 다음 코드가 필요합니다.

std::string hundred_dots((100), ('.'));

인수는 전체 표현식이 아닌 괄호 안에 있습니다. 실제로 C ++ 기능을 사용하려면 C 문법을 약간 벗어나면 식 중 하나만 괄호 안에 있어야합니다. 일이 우리에게 논쟁의 여지가없는 지점으로 인도합니다.

생성자의 인수 없음

eighty_four내 설명 에서 기능을 발견했을 수도 있습니다 .

int eighty_four();

예, 이것은 또한 가장 까다로운 구문 분석의 영향을받습니다. 유효한 정의이며 헤더 파일을 만들었을 때 가장 많이 본 것입니다. 괄호를 추가해도 문제가 해결되지 않습니다.

int eighty_four(());

왜 이렇게이다? 글쎄, ()표현이 아닙니다. C ++에서는 괄호 안에 식을 넣어야합니다. 아무것도 의미하지 않기 auto value = ()때문에 C ++로 작성할 수 없습니다 ()(빈 터플과 같은 경우에도 (파이썬 참조), 0이 아닌 하나의 인수가됩니다). 실제로 이것은 {}괄호 안에 넣을식이 없으므로 함수 선언에 대한 C 문법이 항상 적용되므로 C ++ 11의 구문을 사용하지 않으면 서 속기 구문을 사용할 수 없습니다 .


12

대신에

A a(());

사용하다

A a=A();

32
'더 나은 해결 방법'은 동일하지 않습니다. 0으로 int a = int();초기화 a하고 초기화되지 않은 상태로 int a;둡니다 a. 올바른 해결 방법은 기본 초기화가 원하는 작업을 수행하고 다른 모든 경우에 일관되게 사용하는 경우 A a = {};집계 에 사용 A a;하는 것 입니다. C ++ 11에서는 다음을 사용하십시오A a = A();A a = A();A a {};
Richard Smith

6

당신의 예에서 가장 안쪽의 괄호는 표현식이 될 것이고, C에서 + + 문법은이 정의 expressionassignment-expression또는 다른 expression쉼표 다른 다음 assignment-expression(부록 A.4 - 문법 요약 / 표현식).

이 문법은 또한 assignment-expression다른 여러 유형의 표현 중 하나로 정의 되며 그 중 아무것도 공백이 될 수 없습니다.

그래서 당신이 가질 수없는 이유 A a(())는 단순히 문법이 허용하지 않기 때문입니다. 그러나 C ++을 만든 사람들이 특수한 경우로 빈 parens를 사용하지 않는 이유에 대해 대답 할 수 없습니다-나는 그들이 특별한 경우에 넣지 않을 것이라고 생각합니다. 합리적인 대안.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.