2012-10-22 8 views
8

Ich versuche, C++ 11 Funktionen verwenden, um benutzerdefinierte Stream-Manipulatoren einfacher zu erstellen. Ich kann Lambda-Funktionen als Manipulatoren verwenden, aber nicht std::function<ostream&(ostream&)>.Std :: Funktion als benutzerdefinierte Stream-Manipulator

Hier ist der Code, eingekocht:

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

Die zweite cout Anweisung schlägt mit dem folgenden:

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>&)>]' 

Warum versagt das? Ich benutze cygwin gcc 4.5.3.

Während ich frage, bin ich nicht verrückt nach std::function überall, wegen der Effizienzprobleme. Aber ich möchte Funktionen schreiben, die Lambda-Funktionen zurückgeben, und keine Ahnung haben, wie das geht, ohne std::function. Zum Beispiel wäre so etwas wie das folgende:

auto getAdditionFunctor(); 

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

... aber offensichtlich nicht funktioniert. Gibt es eine alternative Syntax, die funktioniert? Ich kann mir nicht vorstellen, was es sein könnte, also kann ich mit std::function stecken bleiben.

Wenn ich eine Lösung für die zweite Frage hätte, dann wäre die erste Frage strittig.


Vielen Dank.

Definieren operator<<(ostream&, std::function<ostream&(ostream&)> half. Ich hatte eine Webseite falsch gelesen und hatte den Eindruck, dass ostream schlau genug war, ein beliebiges Objekt mit einem operator() als Manipulator zu behandeln. Ich habe mich geirrt. Außerdem wurde die einfache lambda, die ich gebaut habe, wahrscheinlich nur in eine einfache alte Funktion kompiliert, genau wie mir gesagt wurde. In der Tat, wenn ich Variable Capture verwenden, um sicherzustellen, dass lambda keine einfache Funktion ist, schlägt der Compiler fehl. Auch Objekte mit operator() definiert sind nicht (standardmäßig) als Manipulatoren behandelt:

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 
} 

Weitere Update: es stellt sich heraus eine etwas robustere Lösung als die unten diejenigen mit erreicht werden:

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

Dieser Code hat eine zusätzliche const. Ich habe dies entdeckt und versucht, die Lösung in meinem Projekt zu implementieren.

+4

Wollten Sie in diesen Manipulatoren eine "Rückkehr" weglassen? –

+0

Ich hatte gelesen, dass diese impliziert sind, und das Hinzufügen von ihnen hilft nicht. In diesem Fall denke ich, dass sie mit den Rückgaben besser lesbar sind, aber ohne sie sind sie legal. Schließlich funktioniert alles gut, wenn ich 'cout << functionManip; 'ausdepretiere. –

+3

@EdKrohne: 'return' ist ** nicht ** optional hier - Ihre Lambdas rufen UB auf, indem sie einen nicht-leeren" return type "haben und nicht wie bei jeder anderen Funktion einen Wert zurückgeben. §6.6.3/2: "* Das Ende einer Funktion ist gleichbedeutend mit einem' return' ohne Wert; dies führt zu undefiniertem Verhalten in einer Wert-zurückkehrenden Funktion. * "Scheinbar gut zu funktionieren ist nur eine mögliche Manifestation von UB. – ildjarn

Antwort

7

Wenn Sie bei operator<< für ostream aussehen, gibt es keine Überlastung für eine std::function nehmen - und das ist im Grunde, was Sie versuchen, mit cout << functionManip hier zu tun.Um dies zu beheben, entweder definiert die Überlastung selbst:

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

Oder die stream als Argument an die Funktion übergeben:

functionManip(std::cout); 

Was, warum die lambda arbeiten, da der Rückgabetyp eines lambda ist nicht definiert, und es gibt eine Überlastung einen Funktionszeiger für die Verwendung:

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

Das Lambda wahrscheinlich wickelt alles utili Zing eine Struktur und definieren operator(), die in diesem Fall genau wie ein Funktionszeiger funktioniert. Dies ist für mich die wahrscheinlichste Erklärung, hoffentlich kann mich jemand korrigieren, wenn ich falsch liege. Diese

+1

Das Verhalten ist nicht zufällig, es ist vom Standard vorgeschrieben. Ein Lambda, das nichts erfasst, ist * immer * implizit in einen einfachen alten Funktionszeiger konvertierbar. (Und umgekehrt: ein Lambda, das * etwas * erfasst, ist * niemals * implizit in einen Funktionszeiger konvertierbar.) – Quuxplusone

5

ist nicht die Ursache für den Fehler, aber da Sie beide lambdaManip und functionManip als mit dem Rückgabetyp definiert haben ostream& Ich glaube, Sie haben return stream; sowohl hinzuzufügen vergessen.


Der Anruf cout << functionManip schlägt fehl, da es keine operator<<(ostream&, std::function<ostream&(ostream&)> definiert. Fügen Sie einen hinzu und der Anruf wird erfolgreich ausgeführt.

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

Alternativ können Sie functionManip als

functionManip(cout); 

Dies, ohne die operator<< Definition Hinzufügen funktioniert nennen.


Was Ihre Frage über eine Lambda-Rückkehr, da das von getAdditionFunctor zurück Lambda ein Capture-Lambda weniger ist, kann es zu einem Funktionszeiger implizit umgewandelt werden.

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

Guter Punkt, darüber hätte ich für mein Spielzeugbeispiel nachgedacht. Aber was, wenn ich gefangen nehme? Variablenerfassung ist der Punkt, an dem ein "Lambda" von einer Funktion zurückgegeben wird; Wenn ich die variable Aufnahme nicht machen wollte, würde ich einfach eine einfache alte Funktion verwenden. –

+0

+1. Ich wusste nicht, Capture-less Lambdas waren implizit konvertierbar, um Zeiger zu funktionieren, aber es macht Sinn. – Yuushi

3

Die bisherigen Antworten bekamen den Stand der Technik in Ordnung, aber wenn du gehst stark in Ihrem Code mit std::function s und/oder Lambda als Strom Manipulatoren sein, dann können Sie nur die hinzufügen mögen folgende Funktionsvorlage Definition Ihrer Code-Basis, in globalen Bereichen:

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

Der hintere Rückgabetyp es expression SFINAE verwendet, so dass diese insbesondere operator<< Überlastung wird in der Überladungsauflösung nicht einmal teilnehmen, es sei denn m(os) ein wohlgeformtes Ausdruck . (Dies ist, was Sie wollen.)

Dann können Sie auch Dinge wie

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

(Man beachte die völlige Fehlen von irgendwelchen std::function Objekte in dem obigen Code! Versuchen Füllung lambdas in std::function Objekte, es sei denn Sie zu vermeiden absolut müssen, weil die Konstruktion eines std::function Objekts kann teuer sein, kann es sogar Speicherzuweisung beinhalten.)

Es wäre sinnvoll für C++ 17, um diese operator<< Überladung in die Standardbibliothek hinzuzufügen, aber ich bin mir nicht bewusst von irgendwelchen konkreten Vorschlägen in diesem Bereich am Mome nt.

Verwandte Themen