2015-08-12 33 views
10

In particolare Clang 3.6.0, quello attualmente ospitato da Coliru.Clang e le espressioni di piegatura binaria - La maledizione del pacchetto parametri vuoto

Tutti questi frammenti sono chiamati da:

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

Il seguente codice:

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

Attiva il seguente errore di compilazione:

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

Così ho provato a mettere tra parentesi l'espressione :

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

Funziona, ma innesca un avvertimento:

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

Così ho provato a scartare il valore dell'espressione con un cast di tipo funzione per void:

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

Ma:

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

Ho provato anche un static_cast, per lo stesso risultato.

Così ho provato con un C-cast, invece:

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

Ma poi:

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

... e la mia uscita è solo : foo(1, 2, 3); non lo fa più di uscita!

Clang è maledetto da una forza malvagia dagli standard futuri, ha un bug, o il problema è seduto sulla mia sedia in questo momento?

+0

Non riesco a farlo compilare in nessun compilatore. provato MSVC2015 e http://gcc.godbolt.org/ – NathanOliver

+2

'static_cast ((std :: cout << ... << args));' sembra funzionare (cioè doppio paren) e la mia ipotesi è quel clang è corretto, dal momento che un'espressione fold richiede la propria coppia di parentesi –

+0

Ho avuto la stessa impostazione con cin. Se ricordo che il problema è che in qualche modo cerca di espandere '(cin << (1 << 2))' invece di '((cin >> 1) >> 2)' – bolov

risposta

9

È necessario un ulteriore set di parentesi quando si esegue il casting su void utilizzando il cast di notazione funzionale, altrimenti le parentesi vengono considerate parte dell'espressione di cast invece dell'espressione di piegatura. Lo stesso fold expression syntax richiede una serie di parentesi.

Tutti i seguenti lavori senza produrre eventuali avvisi:

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

oppure chiami qualche funzione ostream membro al fine di evitare il risultato inutilizzato avvertimento

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

Come T.C. cita nei commenti qui sotto, il comportamento con (void)(std::cout << ... << args); sembra un bug clang. La sintassi per una notazione cast è specificata in 5.4 [expr.pressofuso]

Cast-espressione:
    unario espressione
    (tipo-id) Cast-espressione

Poiché parentesi non sono richiesti parte dell'espressione cast, quell'uso non dovrebbe produrre avvertimenti e, cosa più importante, dovrebbe risultato nella stampa degli argomenti.

+4

Non vedo perché '(void) (std :: cout << ... << args); 'tuttavia non funziona. Quella parte mi sembra un insetto. –

+6

Segnalato come https://llvm.org/bugs/show_bug.cgi?id=24440 –

+1

Quindi la risposta è "tutti e tre" quindi. Grazie mille, pretorio per la risposta e T.C. per il bug report :) – Quentin

2

Un'espressione volte, da [expr.prim.fold] è:

Un'espressione piega esegue una piega di una confezione parametro di template (14.5.3) per un operatore binario.
        fold-espressione:
                (ghisa espressionefold-operator ...)
                (.. fold-opera torCast-espressione)
                (Cast-espressionearmadio operatore ... armadio operatoreCast-espressione)

Note che in tutti i casi le parentesi fanno parte della grammatica. Così, il vostro esempio iniziale è sintatticamente corretto, e deve essere:

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

Che poi vi darà un avvertimento nel caso di un pacchetto vuoto, dal momento che la piega binario riduce ad appena std::cout; Per sbarazzarsi di tale avviso, è può andare l'itinerario normale di casting per void - solo che il set interno di parentesi fanno parte della grammatica, quindi è necessario due:

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

oppure si può semplicemente gettare un extra endl o simili:

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

o restituire il risultato:

template <class... Args> 
std::ostream& foo(Args... args) { 
    return (std::cout << ... << args); 
} 
2

ho deciso di dare uno sguardo migliore a questo bug nel codice Clang. Ecco la sezione del codice incriminata.Questo caso si verifica quando è appena finito parsing (<type>) ed è ora l'analisi di un'espressione seguente parentesi:

} 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; 
} 

La parte specifica del codice responsabile per questo errore è qui:

return ParseFoldExpression(Result, T); 

Risulta che Result non è mai distinto dal suo valore iniziale true. Credo che dovrebbe essere impostato su ArgExprs.front(), che ora contiene std::cout.

Ora noterete anche il FIXME. Oltre a questo, in particolare, potrebbe valere la pena di aggiustarlo.

Essendo la mia prima correzione di Clang, ho ancora parecchie cose da fare prima di inviare una modifica (per riferimento, Clang 4.0 è attualmente in sviluppo). Sarei più che felice che questo venga risolto, che sia da parte mia o di qualcun altro. Per lo meno, le mie scoperte sono documentate da qualche parte per ora.