2013-06-15 6 views
10

Ho cercato di scrivere un wrapper thread-safe per std :: cout e ho calcolato che è un buon momento per imparare alcuni modelli variadic.C'è qualche trucco che mi permetterebbe di passare manipolatori di flusso ad una funzione di modello variadica?

Like that.

Poi, proprio quando ho pensato ho capito bene, ho notato che non funziona con std :: endl.

Prendete questo codice:

template <typename... P> 
void f(P...){} 

int main() 
{ 
    f(1,2,3,std::endl); 
} 

Quando si tenta di compilarlo, GCC si lamenta in un modo molto stupido:

main.cpp:18:19: error: too many arguments to function 'void f(P ...) [with P = {}]' 

Quando si tenta che con un modello regolare, si ottiene

main.cpp:22:13: error: no matching function for call to 'f(<unresolved overloaded function type>)' 

che in realtà ha senso.

Non è un grosso problema per me, posso farlo in qualche altro modo, ma mi piacerebbe davvero sapere se c'è un modo per aggirare questa limitazione.

risposta

10

Il problema è che i manipolatori come std::endl sono modelli di funzioni. Pertanto, è necessario essere espliciti su quale specializzazione del modello di funzione si desidera passare (in caso contrario, non è possibile alcuna deduzione del tipo).

Per esempio:

f(1, 2, 3, &std::endl<char, std::char_traits<char>>); 
//     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
14

Al posto degli argomenti espliciti template come suggerito da Andy Prowl, vi consiglio modello argomento deduzione:

C:\Temp>type meow.cpp 
#include <iostream> 
#include <utility> 
using namespace std; 

void Print() { } 

template <typename T, typename... Rest> void Print(T&& t, Rest&&... rest) { 
    cout << forward<T>(t); 
    Print(forward<Rest>(rest)...); 
} 

int main() { 
    ostream& (* const narrow_endl)(ostream&) = endl; 

    Print("Hello, world!", narrow_endl, "I have ", 1729, " cute fluffy kittens.", 
     static_cast<ostream& (*)(ostream&)>(endl) 
    ); 
} 

C:\Temp>cl /EHsc /nologo /W4 /MTd meow.cpp 
meow.cpp 

C:\Temp>meow 
Hello, world! 
I have 1729 cute fluffy kittens. 

N3690 13.4 [over.over] specifica le regole di essere usato qui, che torna a C++ 98. Fondamentalmente, prendere l'indirizzo di una funzione sovraccaricata e/o basata su un modello è ambiguo in generale, ma consentito in contesti specifici. Inizializzazione e static_casting sono due di questi contesti, perché forniscono informazioni di tipo sufficienti per disambiguare. Ciò consente alla deduzione degli argomenti del modello di procedere normalmente.

Gli argomenti espliciti del modello sono molto allettanti, ma possono esplodere in tutti i modi. È improbabile che std :: endl venga mai modificato in un modo che rompe gli argomenti espliciti del template qui, ma io raccomando davvero contro di loro (tranne quando le cose sono specificamente progettate per loro, come forward e make_shared).

+0

Quindi non c'è modo di scrivere qualcosa che permetta all'utente di usare i modificatori con i loro nomi regolari, invece di cose come narrow_endl? – user697683

+0

@ user697683 se vuoi scrivere una funzione di stampa avvolgendo un ostream, puoi anche introdurre la funzionalità di wrapping per la formattazione (cioè non permettere di utilizzare i manipolatori ostream nell'interfaccia e fornire alternative). Ma poi si finirebbe con un printf implementato su ostream che è molto probabilmente implementato su C printf. – rubenvb

2

template le funzioni non sono funzioni e std::endl è una funzione template.

Non è possibile passare una funzione template in giro. Tuttavia, è possibile passare un oggetto funzione che rappresenta un sovraccarico attorno.Scrivi tale funtore è piuttosto semplice:

struct endl_overloadset { 
    template<typename... Args> 
    auto operator()(Args&&...args)const 
    ->decltype(std::endl(std::forward<Args>(args))) 
    { return (std::endl(std::forward<Args>(args))) }; 

    template<typename T,typename=typename std::enable_if<\ 
    std::is_same< decltype(static_cast<T>(std::endl)), T >::value\ 
    >::type>\ 
    operator T() const { return std::endl; } 
}; 

ma trovo che, per essere un po 'troppo come boilerplate, quindi scrivere alcune macro che fanno il lavoro per voi:

#define RETURNS(X) ->decltype(X) { return (X); } // C++11 lacks non-lambda return value deduction 
#define OVERLOAD_SET(F) struct {\ 
    template<typename... Args>\ 
    auto operator()(Args&&...args)const\ 
    RETURNS(F(std::forward<Args>(args)...))\ 
    template<typename T,typename=typename std::enable_if<\ 
    std::is_same< decltype(static_cast<T>(F)), T >::value\ 
    >::type>\ 
    operator T() const { return F; }\ 
} 
static OVERLOAD_SET(std::endl) Endl; 

poi passare Endl al numero f e chiamando il numero Endl(Blah) termina facendo std::endl(Blah). Allo stesso modo, assegnare Endl a una variabile o passarlo a un metodo equivale sostanzialmente all'assegnazione di una variabile a std::endl oa un metodo (risoluzione di overload).

Purtroppo, il OVERLOAD_SET non può essere utilizzato all'interno di una funzione, in quanto i tipi locali non possono avere metodi template. Se può essere utilizzato all'interno di una funzione, quindi:

f(1,2,3, OVERLOAD_SET(std::endl)()); 

farebbe quello che volevi. Ma quella sarebbe la lingua che vuoi programmare, non la lingua che hai. (Ancora meglio sarebbe la proposta di @ Xeo di permettere che i functor del set di overload vengano generati automaticamente usando qualche ulteriore abuso casuale della sintassi [], piuttosto che fare affidamento sui macro).

Live example, dove passo il mio endl_functor ad un print metodo e quindi utilizzare << su di esso senza ulteriori indugi.

+0

Nota che un tale manipolatore trasformato non può più essere usato come uno reale: 'std :: cout << OVERLOAD_SET (std :: endl) {};' (o anche 'std :: cout << [] std: : endl; ') non verrà compilato perché questi due non producono funzioni normali, ma oggetti funzione. – Xeo

+0

@xeo hmm: l'aggiunta di un 'operatore T' potrebbe funzionare. – Yakk

+0

@xeo 'operatore T' aggiunto e testato! 'endl_functor' ora converte nello stesso modo in cui' std :: endl' convertirà in. – Yakk

Problemi correlati