2012-10-22 13 views
8

Sto cercando di utilizzare le funzionalità di C++ 11 per rendere più semplice la creazione di manipolatori di flusso personalizzati. Posso usare le funzioni lambda come manipolatori, ma non std::function<ostream&(ostream&)>.std :: funziona come un manipolatore di flusso personalizzato

Ecco il codice, si riduceva:

#include <iostream> 
#include <functional> 
using namespace std; 

auto lambdaManip = [] (ostream& stream) -> ostream& { 
    stream << "Hello world" << endl; 
}; 
function<ostream& (ostream&)> functionManip = [] (ostream& stream) -> ostream& { 
    stream << "Hello world" << endl; 
}; 

int main (int argc, char** argv) { 
    cout << lambdaManip; // OK 
    cout << functionManip; // Compiler error 
} 

Il secondo cout dichiarazione riesce con il seguente:

g++-4 src/Solve.cpp -c -g -std=c++0x -o src/Solve.o -I/home/ekrohne/minisat 
src/Solve.cpp: In function 'int main(int, char**)': 
src/Solve.cpp:24:11: error: cannot bind 'std::ostream' lvalue to 'std::basic_ostream<char>&&' 
/usr/lib/gcc/i686-pc-cygwin/4.5.3/include/c++/ostream:579:5: error: initializing argument 1 of 'std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char, _Traits = std::char_traits<char>, _Tp = std::function<std::basic_ostream<char>&(std::basic_ostream<char>&)>]' 

Perché questo fallire? Sto usando cygwin gcc 4.5.3.

Mentre sto chiedendo, non sono pazzo di usare std::function ovunque, a causa dei problemi di efficienza. Ma desidero scrivere funzioni che restituiscano funzioni lambda e non ho idea di come farlo senza std::function. Ad esempio, qualcosa di simile al seguente sarebbe fantastico

auto getAdditionFunctor(); 

auto getAdditionFunctor() { 
    return [] (int x, int y) { return x + y }; 
}; 

... ma ovviamente non funziona. C'è una sintassi alternativa che funziona? Non riesco a immaginare cosa potrebbe essere, quindi potrei essere bloccato con std::function.

Se avessi una soluzione alla seconda domanda, allora la prima domanda sarebbe discutibile.


Grazie.

Definire operator<<(ostream&, std::function<ostream&(ostream&)> aiutato. Avevo letto erroneamente una pagina Web e avevo l'impressione che lo ostream fosse abbastanza intelligente da trattare un oggetto arbitrario con un operator() come manipolatore. Ho sbagliato su questo. Inoltre, il semplice lambda che ho costruito probabilmente si stava solo compilando in una semplice vecchia funzione, proprio come mi era stato detto. Infatti, se utilizzo la cattura variabile per garantire che lo lambda non sia una funzione semplice, il compilatore fallisce. Inoltre, gli oggetti con operator() definiti (per default) trattati come manipolatori sono:

class Manipulator { 
    ostream& operator()(ostream& stream) const { 
     return stream << "Hello world" << endl; 
    }; 
} classManip; 

function<ostream& (ostream&)> functionManip = [] (ostream& stream) -> ostream& { 
    return stream << "Hello world" << endl; 
}; 

int main (int argc, char** argv) { 
    const string str = "Hello world"; 
    auto lambdaManip = [&] (ostream& stream) -> ostream& { 
     return stream << str << endl;  
    }; 

    cout << classManip;  // Compiler error 
    cout << lambdaManip; // Compiler error 
    cout << functionManip; // Compiler error 
} 

Ulteriore aggiornamento: si scopre una soluzione leggermente più robusti di quelli indicati può essere realizzato con:

// Tell ostreams to interpret std::function as a 
// manipulator, wherever it sees one. 
inline ostream& operator<<(
     ostream& stream, 
     const function<ostream& (ostream&)>& manipulator) { 
    return manipulator(stream); 
} 

Questo codice ha un extra const. Ho scoperto questo cercando di implementare effettivamente la soluzione nel mio progetto.

+4

Intendevi lasciare un "ritorno" in quei manipolatori? –

+0

Avevo letto che quelli sono impliciti e aggiungerli non aiuta. In questo caso penso che siano più leggibili con i ritorni, ma sono legali senza. Dopotutto, tutto funziona perfettamente se commento "cout << functionManip;". –

+3

@EdKrohne: 'return' è ** not ** opzionale qui - i tuoi lambdas invocano UB avendo un tipo di ritorno non -'void' e non restituiscono un valore, proprio come con qualsiasi altra funzione. §6.6.3/2: "* Scorrere alla fine di una funzione equivale a un' return' senza valore, il che comporta un comportamento indefinito in una funzione di ritorno del valore. * "Apparire per funzionare correttamente è solo una possibile manifestazione di UB. – ildjarn

risposta

7

Se si guarda alla operator<< per ostream, non c'è sovraccarico per prendere una std::function - e questo è fondamentalmente ciò che si sta cercando di fare qui con cout << functionManip.Per risolvere questo problema, sia definire il sovraccarico di te stesso:

ostream& operator<<(ostream& os, std::function<ostream& (ostream&)>& s) 
{ 
    return s(os); 
} 

Oppure passare il stream come argomento per la funzione:

functionManip(std::cout); 

Per quanto riguarda il motivo per cui la lambda sta lavorando, visto che il tipo di ritorno di un lambda è indefinito, e non v'è un sovraccarico per l'utilizzo di un puntatore a funzione:

ostream& operator<< (ostream& (*pf)(ostream&)); 

La lambda è probabilmente avvolgendo tutto Utili zing a a struct e definendo operator() che in questo caso funzionerà esattamente come un puntatore a funzione. Questa per me è la spiegazione più probabile, spero che qualcuno possa correggermi se sbaglio.

+1

Il comportamento non è casuale, è richiesto dallo Standard. Un lambda che non cattura nulla è * sempre * implicitamente convertibile in un semplice puntatore a funzione vecchia. (E inversamente: un lambda che * fa * cattura qualcosa è * mai * implicitamente convertibile in un puntatore di funzione.) – Quuxplusone

5

questa non è la causa dell'errore, ma dal momento che hai definito sia lambdaManip e functionManip ad avere il tipo di ritorno ostream& Credo che hai dimenticato di aggiungere return stream; ad entrambi.


La chiamata cout << functionManip fallisce perché non c'è operator<<(ostream&, std::function<ostream&(ostream&)> definita. Aggiungi uno e la chiamata avrà successo.

ostream& operator<<(ostream& stream, function<ostream& (ostream&)>& func) { 
    return func(stream); 
} 

In alternativa, è possibile chiamare functionManip come

functionManip(cout); 

Ciò funzionerà senza aggiungere la definizione operator<<.


Per quanto riguarda le tue domande su restituzione di un lambda, dal momento che il lambda restituito da getAdditionFunctor è una lambda cattura-less, può essere convertito in modo implicito un puntatore a funzione.

typedef int(*addition_ptr)(int,int); 
addition_ptr getAdditionFunctor() 
{ 
    return [] (int x, int y) -> int { return x + y; }; 
} 

auto adder = getAdditionFunctor(); 
adder(10,20); // Outputs 30 
+0

Buon punto, avrei dovuto pensarci per il mio esempio di giocattolo. Ma cosa succede se sto catturando? La cattura variabile è il punto di ritorno di un 'lambda' da una funzione; se non volessi fare la cattura delle variabili, dovrei semplicemente usare una semplice vecchia funzione in primo luogo. –

+0

+1. Non sapevo che i lambda senza cattura fossero implicitamente convertibili in puntatori di funzioni, ma ha perfettamente senso. – Yuushi

3

Le risposte precedenti hanno ottenuto lo stato del diritto d'arte, ma se avete intenzione di essere pesantemente utilizzando std::function s e/o lambda come manipolatori flusso nel codice, allora si potrebbe desiderare solo aggiungere il seguente definizione di funzione di modello per il vostro codice di base, in ambito globale:

template<class M> 
auto operator<< (std::ostream& os, const M& m) -> decltype(m(os)) 
{ 
    return m(os); 
} 

Il tipo restituito finale ci usi expression SFINAE, quindi questo particolare operator<< sovraccarico non sarà nemmeno partecipare nella risoluzione di sovraccarico a meno m(os) è un'espressione ben formata . (Questo è ciò che si desidera.)

Poi si può anche fare cose come

template<class T> 
auto commaize(const T& t) 
{ 
    return [&t](std::ostream& os) -> std::ostream& { 
     return os << t << ", "; 
    }; 
} 

int main() 
{ 
    std::cout << commaize("hello") << commaize("darling") << std::endl; 
} 

(Notare la totale mancanza di qualsiasi std::function oggetti nel codice di cui sopra! Cercate di evitare di lambda ripieno in std::function oggetti a meno che non assolutamente necessario, perché la costruzione di un oggetto std::function può essere costosa, può anche coinvolgere l'allocazione di memoria.)

Sarebbe sensato per C++ 17 aggiungere questo sovraccarico operator<< alla libreria standard, ma non sono a conoscenza di ogni proposta concreta in quella zona alla mamma nt.

Problemi correlati