В чем разница между этими двумя версиями кода?
Этот код вызывает ошибку компиляции (самый неприятный анализ)
#include <iostream>
class A {
int a;
public:
A(int x) :a(x) {}
};
class B {
public:
B(const A& obj) { std::cout << "B\n";}
void foo() {std::cout << "foo\n";}
};
int main()
{
int test = 20;
B var(A(test)); //most vexing parse
var.foo();
return 0;
}
Но если я пройду 20
вместо test
(A(20)
вместо A(test)
), ошибки компиляции нет.
#include <iostream>
class A {
int a;
public:
A(int x) :a(x) {}
};
class B {
public:
B(const A& obj) { std::cout << "B\n";}
void foo() {std::cout << "foo\n";}
};
int main()
{
int test = 20;
//B var(A(test));
B var(A(20)); //ok works fine
var.foo();
return 0;
}
Почему это не считается самым неприятным разбором? В чем разница между этими двумя версиями кода?
2 ответа
Переменная может быть определена как
type(name)
Из-за этого
B var(A(test));
объявляет функцию с именем var
который возвращает B
и берет A
названный test
, В
B var(A(20));
если вы пытались сделать то же самое, A
параметр будет называться 20
, который не является допустимым именем переменной. Поскольку это не может быть именем переменной, мы знаем, что это значение, и вместо этого мы создаем переменную с именем var
типа B
со значением A(20)
,
Наиболее неприятный вопрос - проблема грамматики, а не семантики. Грамматически, A(test)
сводится к identifier : OPEN_PAREN : identifier : CLOSE_PAREN
, В контексте это неоднозначно, потому что вторым идентификатором может быть имя переменной или имя типа. Компилятор должен выбрать способ интерпретации этой последовательности токенов, и ни один из них не является неправильным.
В отличие от A(20)
сводится к identifier : OPEN_PAREN : integer_literal : CLOSE_PAREN
, Целочисленный литерал нельзя интерпретировать как идентификатор, поэтому нет способа интерпретировать его как имя типа. Поэтому он должен быть проанализирован как выражение, которое инициализирует объект типа A
,