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 в настоящее время находится в разработке). Я был бы более чем счастлив, если бы это было исправлено вообще, будь то я или кто-то другой. По крайней мере, мои результаты задокументированы где-то сейчас.