g++ отклоняет, clang++ принимает: foo(x)("bar")("baz");
Кто-то спросил на днях, почему что-то компилируется с помощью clang, а не с gcc. Я интуитивно понял, что происходит, и смог помочь человеку, но это заставило меня задуматься - согласно стандарту, какой компилятор был правильным? Вот урезанная версия кода:
#include <iostream>
#include <string>
class foo
{
public:
foo(const std::string& x):
name(x)
{ }
foo& operator()(const std::string& x)
{
std::cout << name << ": " << x << std::endl;
return (*this);
}
std::string name;
};
int main()
{
std::string x = "foo";
foo(x)("bar")("baz");
return 0;
}
Это хорошо компилируется с clang ++, но g ++ выдает следующую ошибку:
runme.cpp: In function ‘int main()’:
runme.cpp:21:11: error: conflicting declaration ‘foo x’
foo(x)("bar")("baz");
^
runme.cpp:20:17: error: ‘x’ has a previous declaration as ‘std::string x’
std::string x = "foo";
Если я добавлю пару скобок в строку 21, g++ будет счастлив:
(foo(x))("bar")("baz");
Другими словами, g ++ интерпретирует эту строку как:
foo x ("bar")("baz");
Мне кажется, это ошибка в g ++, но я снова хотел спросить стандартных экспертов, какой компилятор ошибся?
PS: gcc-4.8.3, clang-3.5.1
2 ответа
Насколько я могу судить, это описано в разделе стандарта C++. 6.8
Разрешение неоднозначности, которое говорит, что может быть неоднозначность между выражениями и декларациями, и гласит:
В грамматике существует двусмысленность, связанная с выражениями-операторами и объявлениями: оператор выражения с явным преобразованием типа в стиле функции (5.2.3) в качестве крайнего левого подвыражения может быть неотличим от объявления, где первый декларатор начинается с (. В этих случаях оператор является декларацией. [Примечание: Чтобы устранить неоднозначность, может потребоваться изучить весь оператор, чтобы определить, является ли он выражением-выражением или объявлением. Это устраняет неоднозначность во многих примерах. [Пример: предполагается, что T является простым типом -спецификатор (7.1.6),
и приводит следующие примеры:
T(a)->m = 7; // expression-statement
T(a)++; // expression-statement
T(a,5)<<c; // expression-statement
T(*d)(int); // declaration
T(e)[5]; // declaration
T(f) = { 1, 2 }; // declaration
T(*g)(double(3)); // declaration
а потом говорит:
Остальные случаи являются декларациями. [ Пример:
class T { // ... public: T(); T(int); T(int, int); }; T(a); // declaration T(*b)(); // declaration T(c)=7; // declaration T(d),e,f=3; // declaration extern int h; T(g)(h,2); // declaration
- конец примера] - конец заметки]
Кажется, что этот случай попадает в примеры декларации, в частности, последний пример, кажется, делает случай в OP, поэтому gcc
было бы правильно тогда.
Соответствующий раздел, упомянутый выше 5.2.3
Явное преобразование типов (функциональная запись) говорит:
[...] Если указанный тип является типом класса, тип класса должен быть завершен. Если в списке выражений указано более одного значения, тип должен быть классом с соответствующим образом объявленным конструктором (8.5, 12.1), а выражение T(x1, x2, ...) фактически эквивалентно объявлению T t(х1, х2, ...); для некоторой изобретенной временной переменной t, результатом которой является значение t в качестве значения.
а также 8.3
Значение деклараторов, которое говорит:
В декларации T D, где D имеет вид
( D1 )
тип содержимого объявления-идентификатора совпадает с типом содержимого объявления-объявления в объявлении
T D1
Круглые скобки не изменяют тип встроенного идентификатора объявления, но они могут изменять привязку сложных объявлений.
Обновить
Первоначально я использовал N337, но если мы посмотрим на раздел N4296 6.8
был обновлен и теперь включает следующее примечание:
Если оператор не может синтаксически быть объявлением, нет двусмысленности, поэтому это правило не применяется.
Который означает, что gcc
неверно, так как:
foo x ("bar")("baz");
не может быть действительной декларации, я изначально интерпретировал пункт 2
как сказать, если ваше дело начинается с любого из следующего, то это декларация, которая, возможно, как gcc
интерпретатор интерпретируется также.
Я должен был быть более подозрительным к пункту 2
единственная нормативная часть абзаца 2
действительно ничего не сказал относительно пункта 1
и, кажется, предъявляет требование к примеру, который не является нормативным. Мы можем видеть, что это утверждение формы параграфа 2
теперь на самом деле примечание, которое имеет гораздо больше смысла.
Как указано ниже в ТК, пункт 2
на самом деле никогда не был нормативным, он просто появился таким образом, и он связался с изменениями, которые это исправили.
Если мы удалим линию
std::string x = "foo";
тогда g ++ жалуется на:
foo(x)("bar")("baz");
с синтаксической ошибкой:
foo.cc:20:18: error: expected ',' or ';' before '(' token
foo(x)("bar")("baz");
Я не вижу как foo (x)("bar")("baz");
может быть допустимым объявлением, и, очевидно, g ++ тоже не может. Линия foo x("bar")("baz");
отклоняется с той же ошибкой.
"Разрешение неоднозначности", упомянутое в посте Шафика, вступает в силу только тогда, когда выражение-выражение синтаксически неотличимо от объявления. Однако в этом случае это недопустимый синтаксис объявления, поэтому нет никакой двусмысленности, это должен быть оператор-выражение.
g ++ не может обработать строку как выражение-выражение, поэтому это ошибка g ++.
Это очень похоже на эту ошибку g ++, недавно обсуждаемую в SO; кажется, что g ++, возможно, слишком рано решает, что строка должна быть объявлением.