Clang и двоичные складывающиеся выражения - проклятие пустого пакета параметров
В частности, Clang 3.6.0, который в настоящее время размещен Coliru.
Все эти фрагменты вызываются из:
int main() {
foo();
std::cout << "\n----\n";
foo(1, 2, 3);
}
Следующий код:
template <class... Args>
void foo(Args... args) {
std::cout << ... << args;
}
Запускает следующую ошибку компиляции:
main.cpp:7:17: error: expected ';' after expression
std::cout << ... << args;
^
;
main.cpp:7:15: error: expected expression
std::cout << ... << args;
^
Поэтому я попытался помещать круглые скобки вокруг выражения:
(std::cout << ... << args);
Он работает, но вызывает предупреждение:
main.cpp:7:6: warning: expression result unused [-Wunused-value]
(std::cout << ... << args);
^~~~~~~~~
main.cpp:11:5: note: in instantiation of function template specialization 'foo<>' requested here
foo();
^
Итак, я попытался отбросить значение выражения с помощью приведения типа функции к void
:
void(std::cout << ... << args);
Но:
main.cpp:7:20: error: expected ')'
void(std::cout << ... << args);
^
main.cpp:7:9: note: to match this '('
void(std::cout << ... << args);
^
Я тоже попробовал a static_cast
для того же результата.
Итак, я попробовал вместо C-cast:
(void)(std::cout << ... << args);
Но тогда:
main.cpp:6:18: warning: unused parameter 'args' [-Wunused-parameter]
void foo(Args... args) {
^
... и мой вывод только ----
: foo(1, 2, 3);
больше не выводится!
Является ли Кланг проклят злой силой из будущих стандартов, есть ли у нее ошибка или проблема на моем стуле сейчас?
Ответы
Ответ 1
Для приведения в класс void
вам потребуется дополнительный набор круглых скобок, в то время как круглые скобки считаются частью литого выражения вместо выражения fold. Для fold expression синтаксиса требуется набор круглых скобок.
Вся последующая работа без каких-либо предупреждений:
void((std::cout << ... << args));
(void)((std::cout << ... << args));
Или просто вызовите функцию члена ostream
, чтобы избежать предупреждения о неиспользуемом результате
(std::cout << ... << args).flush();
Как T.C. упоминается в комментариях ниже, поведение с (void)(std::cout << ... << args);
кажется ошибкой clang. Синтаксис для нотной маркировки указан в 5.4 [expr.cast]
монолитно-выражение:
Унарное выражение
(type-id) cast-expression
Поскольку скобки не требуются как часть выраженного выражения, это использование не должно вызывать предупреждения, и что более важно, это должно привести к печати аргументов.
Ответ 2
Сглаженное выражение, из [expr.prim.fold]:
Сгибающее выражение выполняет свертку пакета параметров шаблона (14.5.3) над двоичным оператором.
складчато-выражение:
(сбрасывание-выражение-оператор-оператор...)
(... fold-operator cast-expression)
"" "" "" ""
Обратите внимание, что во всех случаях скобки являются частью грамматики. Таким образом, ваш первоначальный пример синтаксически неверен и должен быть:
template <class... Args>
void foo(Args... args) {
(std::cout << ... << args);
}
Затем вы получите предупреждение в случае пустого пакета, так как двоичная справка сводится к просто std::cout;
Чтобы избавиться от этого предупреждения, вы можете пойти обычным способом кастинга на void
- только это внутренний набор круглых скобок является частью грамматики, поэтому вам нужно два:
void((std::cout << ... << args));
Или вы можете просто добавить лишний endl
или тому подобное:
(std::cout << ... << args) << std::endl;
Или верните результат:
template <class... Args>
std::ostream& foo(Args... args) {
return (std::cout << ... << args);
}
Ответ 3
Я решил лучше рассмотреть эту ошибку в источнике Clang. Вот оскорбительный раздел кода. Этот случай случается, когда он только что закончил синтаксический анализ (<type>)
и теперь разбирает следующее выражение в скобках:
} else if (isTypeCast) {
// Parse the expression-list.
InMessageExpressionRAIIObject InMessage(*this, false);
ExprVector ArgExprs;
CommaLocsTy CommaLocs;
if (!ParseSimpleExpressionList(ArgExprs, CommaLocs)) {
// FIXME: If we ever support comma expressions as operands to
// fold-expressions, we'll need to allow multiple ArgExprs here.
if (ArgExprs.size() == 1 && isFoldOperator(Tok.getKind()) &&
NextToken().is(tok::ellipsis))
return ParseFoldExpression(Result, T);
ExprType = SimpleExpr;
Result = Actions.ActOnParenListExpr(OpenLoc, Tok.getLocation(),
ArgExprs);
}
}
// The beginning of ParseFoldExpression(LHS, T):
if (LHS.isInvalid()) {
T.skipToEnd();
return true;
}
Определенная часть кода, ответственного за эту ошибку, находится здесь:
return ParseFoldExpression(Result, T);
Оказывается, что Result
никогда не устанавливается отдельно от его начального значения true
. Я считаю, что он должен быть установлен на ArgExprs.front()
, который теперь содержит std::cout
.
Теперь вы также заметите FIXME. Несмотря на то, что это не связано с этой проблемой, возможно, стоит исправить это.
Будучи моим первым исправлением Clang, у меня все еще есть несколько вещей, прежде чем отправлять изменения (для справки, Clang 4.0 в настоящее время находится в разработке). Я был бы более чем счастлив, если бы это было исправлено вообще, будь то я или кто-то другой. По крайней мере, мои результаты задокументированы где-то сейчас.