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 ++, возможно, слишком рано решает, что строка должна быть объявлением.

Другие вопросы по тегам