Search code examples
c++clangcompiler-bugc++17fold-expression

Clang and the binary fold expressions — The curse of the empty parameter pack


Specifically Clang 3.6.0, the one currently hosted by Coliru.

All these snippets are called from :

int main() {
    foo();
    std::cout << "\n----\n";
    foo(1, 2, 3);
}

The following code :

template <class... Args>
void foo(Args... args) {
    std::cout << ... << args;
}

Triggers the following compilation error :

main.cpp:7:17: error: expected ';' after expression
    std::cout << ... << args;
                ^
                ;
main.cpp:7:15: error: expected expression
    std::cout << ... << args;
              ^

So I tried putting parentheses around the expression :

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

It works, but triggers a warning :

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

So I tried to discard the value of the expression with a function-style cast to void :

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

But :

main.cpp:7:20: error: expected ')'
    void(std::cout << ... << args);
                   ^
main.cpp:7:9: note: to match this '('
    void(std::cout << ... << args);
        ^

I tried a static_cast too, for the same result.

So I tried with a C-cast instead :

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

But then :

main.cpp:6:18: warning: unused parameter 'args' [-Wunused-parameter]
void foo(Args... args) {
                 ^

... and my output is only ---- : foo(1, 2, 3); doesn't output anymore !

Is Clang cursed by an evil force from future standards, does it have a bug, or is the problem sitting on my chair right now ?


Solution

  • You need an extra set of parentheses when casting to void using the functional notation cast, otherwise the parentheses are considered part of the cast expression instead of the fold expression. The fold expression syntax itself requires a set of parentheses.

    All of the following work without producing any warnings:

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

    Or just call some ostream member function to avoid the unused result warning

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

    As T.C. mentions in the comments below, the behavior with (void)(std::cout << ... << args); seems like a clang bug. The syntax for a cast notation is specified in 5.4 [expr.cast]

    cast-expression:
      unary-expression
      ( type-id ) cast-expression

    Since parentheses are not required as part of the cast expression, that usage shouldn't be producing warnings, and more importantly, it should result in printing the arguments.