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);
        ^

Я тоже попробовал 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); больше не выводится!

Проклят ли Clang злой силой из будущих стандартов, есть ли в нем ошибка или проблема сидит на моем стуле прямо сейчас?


person Quentin    schedule 12.08.2015    source источник
comment
Я не могу заставить это скомпилировать ни один компилятор. попробовал MSVC2015 и gcc.godbolt.org   -  person NathanOliver    schedule 12.08.2015
comment
static_cast<void>((std::cout << ... << args)); похоже работает (то есть двойные скобки), и я предполагаю, что clang правильный, так как выражение fold требует своей собственной пары скобок   -  person Piotr Skotnicki    schedule 12.08.2015
comment
У меня была такая же установка с cin. Насколько я помню, проблема в том, что он каким-то образом пытается расширить (cin << (1 << 2)) вместо ((cin >> 1)>> 2)   -  person bolov    schedule 12.08.2015


Ответы (3)


Вам нужен дополнительный набор круглых скобок при приведении к void с использованием приведения функциональной нотации, в противном случае круглые скобки считаются частью выражения приведения, а не выражения сгиба. Сам синтаксис выражения fold требует набора круглых скобок.

Все следующие действия работают без каких-либо предупреждений:

void((std::cout << ... << args));
(void)((std::cout << ... << args));

Или просто вызовите какую-нибудь функцию-член ostream, чтобы избежать предупреждения о неиспользованном результате.

(std::cout << ... << args).flush();

Как Т.С. упоминает в комментариях ниже, поведение с (void)(std::cout << ... << args); похоже на ошибку clang. Синтаксис нотации приведения указан в 5.4 [expr.cast].

приведение-выражение:
унарное-выражение
( type-id ) приведение-выражение

Поскольку круглые скобки не требуются как часть выражения приведения, такое использование не должно вызывать предупреждений и, что более важно, должно приводить к печати аргументов.

person Praetorian    schedule 12.08.2015
comment
Однако я не понимаю, почему (void)(std::cout << ... << args); не работает. Эта часть кажется мне ошибкой. - person T.C.; 12.08.2015
comment
Тогда ответ - все три. Большое спасибо, Praetorian за ответ и T.C. за отчет об ошибке :) - person Quentin; 12.08.2015

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

person chris    schedule 14.11.2016

Выражение сгиба из [expr.prim.fold]:

Выражение fold выполняет сворачивание пакета параметров шаблона (14.5.3) над бинарным оператором.
fold-expression:
( cast-expression < em>оператор-сгиба ... )
( ... оператор-сгиба выражение-приведения )
( приведение- выражение оператор-свертки ... оператор-свертки выражение-приведения )

Обратите внимание, что во всех случаях скобки являются частью грамматики. Итак, ваш первоначальный пример синтаксически неверен и должен быть:

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);
}
person Barry    schedule 12.08.2015