danlei schriebKönntest Du mir dann vielleicht das Präprozessor-Äquivalent, oder eine Lösung basierend auf den anderen von Dir erwähnen Optionen zeigen? Vielleicht das SQL-Beispiel?
Also für SQL gibt es verschiedenste Ansätze, mit C-Präprozessor, mit eigenem, proprietärem Datenbank-Präprozessor (z.B. proc von Oracle, sog. embedded SQL), über ODBC (C-Funktionen) oder mit C++-Klassen.

Heutzutage will man meist die konkret verwendete Datenbank offenlassen, d.h. das Programm muß auch hinsichtlich des verwendeten SQLs offenbleiben, was keine leichte Aufgabe ist. Es ist darum klug, alles spezifische SQL in eine spezifische Datenbankklasse zu kapseln. Ich mache darum eine Klasse für jede unterstützte Datenbank. Diese Klassen haben überschriebene Methoden gleichen Namens, aber eben mit spezifischer Implementierung. Die Erzeugung einer Instanz übernimmt eine Factory, die alle dazu erforderlichen Parameter bekommt, hier einen Datenbank-Typ und einen spezifischen Connection-string, der bei SQLite nicht mehr als den Dateinamen enthalten muß. Vielleicht würde auch nur der connection string genügen, die Factory müßte anhand seiner dann herausfinden, was für eine Datenbank geöffnet werden soll. Diese liefert jedenfalls nur einen polymorphen Zeiger zurück, damit das Hauptprogramm unabhäng von der konkreten Datenbank bleiben kann, es kennt nicht einmal die konkrete Klasse:
enum DBType {SQLite, MySQL, Oracle, DB2, sonstwas};

struct Record
{
   string column1;
   string column2;
   int column3;
}

ostream& operator << (ostream& os, const Record& r)
{
   return os << r.column1 << ", " << r.column2 << ", " << r.column3;
}

// ... main ...
try
{
   shared_ptr<Database> db(DBFactory::Create(SQLite, "connection_string"));
   vector<Record> records;
   db->GetMyDesiredValues(records);
   foreach (const Record& r, records)
      cout << r << endl;
}
catch (Database::Exception)
{
}
Der vector<Record> ist old style. Man könnte heute auch sehr elegant ein zweidimensionales boost::multi_array einsetzen und bekäme quasi eine direkte Abbildung einer SQL-Tabelle, mit wahlfreiem Zugriff über [Zeile][Spalte] sowie echten NULL-Werten in leeren Feldern. Man könnte in C++ dann auch einen Operator definieren, der SQL entgegennimmt und ein vollständiges Ergebnis zurückgibt:
const multi_array<any,2>& table = (*db) << "select * from MyTable;";
Das halte ich für wirklich cool. Dieser SQL-<<-Operator wäre allerdings insofern komplizierter als oben benutzte Methode GetMyDesiredValues, weil diese weiß, wie das Ergebnis aussehen soll und genau dieses holt, während der Operator aufgrund seines universellen Anspruchs sich erst einmal über die Gestalt des Ergebnisses klarwerden müßte. Er könnte allerdings dieses SQL vor der Ausführung auch noch bearbeiten, z.B. datenbankspezifisch machen, implementiert ist er ja in der Datenbankklasse.

Sehr interessante Implementierungen solcher Operatoren (und solcher Datenbankklassen) findet man beispielsweise in der Bibliothek PoCo. Man studiere einmal dieses sehr einfache Beispiel (noch zwei Zeilen mehr und es wäre wenigstens transaktionssicher). Man beachte insbesondere auch unten die zwei Zeilen zur Ausgabe einer völlig (!) beliebigen (!) Selektion.

Muß mich leider aus Zeitmangel vorerst ausklinken. Wochenende ist immer bißchen heikel.
T.M. schrieb Heutzutage will man meist die konkret verwendete Datenbank offenlassen, d.h. das Programm muß auch hinsichtlich des verwendeten SQLs offenbleiben, was keine leichte Aufgabe ist. Es ist darum klug, alles spezifische SQL in eine spezifische Datenbankklasse zu kapseln. Ich mache darum eine Klasse für jede unterstützte Datenbank. Diese Klassen haben überschriebene Methoden gleichen Namens, aber eben mit spezifischer Implementierung. Die Erzeugung einer Instanz übernimmt eine Factory, die alle dazu erforderlichen Parameter bekommt, hier einen Datenbank-Typ und einen spezifischen Connection-string, der bei SQLite nicht mehr als den Dateinamen enthalten muß. Vielleicht würde auch nur der connection string genügen, die Factory müßte anhand seiner dann herausfinden, was für eine Datenbank geöffnet werden soll. Diese liefert jedenfalls nur einen polymorphen Zeiger zurück, damit das Hauptprogramm unabhäng von der konkreten Datenbank bleiben kann, es kennt nicht einmal die konkrete Klasse:
Zwar magst Du damit Recht haben, dass man die Datenbank geöffnet lassen will, aber in diesem Fall wurde ja vorgegeben, dass sie nur innerhalb eines syntaktisch etablierten Kontextes geöffnet sein solle und eine Funktion query innerhalb dieses Kontextes je nach verwendeter db das Richtige(tm) macht.

Insofern können wir unser beider Beispiele nur schlecht miteinander vergleichen; während meine Lösung eben einen solchen Kontext etabliert, muss man dies in deinem Ansatz nach wie vor händisch tun:
// ... main ...
try
{
   shared_ptr<Database> db(DBFactory::Create(SQLite, "connection_string"));
   vector<Record> records;
   db->GetMyDesiredValues(records);
   foreach (const Record& r, records)
      cout << r << endl;
}
catch (Database::Exception)
{
}
Dem steht nun beispielsweise dies gegenüber:
(with-sql (sqlite "connection_string")
  (format t "~{~a~%~}~%" (query "select ...")))
Das andere Beispiel:
const multi_array<any,2>& table = (*db) << "select * from MyTable;";
Vergleichen wir nun diesen Aufruf mit meiner Lösung, so entspricht dies innerhalb eines angenommenen Kontextes (sprich innerhalb with-sql):
(setq result (query "select * from MyTable;"))
Oder, so man nicht innerhalb des Kontextes zuweisen, sondern den Kontext das Ergebnis direkt zurückliefern lassen will:
(query "select * from MyTable;")
Welche dieser Zeilen nun komplexer ist, kann jeder für sich selbst entscheiden. Fest steht, dass, ohne Berücksichtigung der durch die explizite statische Typisierung entstandenen erhöhten Komplexität, Deine Lösung eben keinen Kontext etabliert und <<, welches ja das Äquivalent zum query meines Ansatzes ist, ein explizites Datenbankobjekt verlangt, während dies bei mir (gemäß der Anforderungen) implizit ist.

Du magst damit Recht haben, dass man eher die db geöffnet lassen will, aber der "Auftraggeber" wollte nun einmal etwas anderes.

Wollte man Deinen Ansatz in Lisp umsetzen, wäre es idiomatisch, eine generische Funktion query zu benutzen, welche auf dem Typ eines Datenbankobjektes dispatcht und dementsprechend Methoden dieser generischen Funktion für die einzelnen Datenbanktypen zu implementieren.
Muß mich leider aus Zeitmangel vorerst ausklinken. Wochenende ist immer bißchen heikel.
Kein Problem, danke für das Beispiel.
danlei schrieb
T.M. schriebHeutzutage will man meist die konkret verwendete Datenbank offenlassen, d.h. das Programm muß auch hinsichtlich des verwendeten SQLs offenbleiben,
Zwar magst Du damit Recht haben, dass man die Datenbank geöffnet lassen will,
Nein, ich meinte "offenhalten" im Sinne von "offenlassen, welche man konkret benutzt".
danlei schriebDem steht nun beispielsweise dies gegenüber:
(with-sql (sqlite "connection_string")
  (format t "~{~a~%~}~%" (query "select ...")))
Welche dieser Zeilen nun komplexer ist, kann jeder für sich selbst entscheiden. Fest steht, dass, ohne Berücksichtigung der durch die explizite statische Typisierung entstandenen erhöhten Komplexität, Deine Lösung eben keinen Kontext etabliert und <<, welches ja das Äquivalent zum query meines Ansatzes ist, ein explizites Datenbankobjekt verlangt, während dies bei mir (gemäß der Anforderungen) implizit ist.
Achso, es ging wirklich um Kürze. Nichts leichter als das. Unter Benutzung von PoCo würde ich dann schreiben:
cout << RecordSet(
            Session("SQLite","sample.db"),
            "SELECT * FROM MyTable"
        );
Ein voll lauffähiger (nicht einmal fiktiver) Einzeiler unter Benutzung zweier unbenannter, temporärer Objekte (RecordSet und Session). Öffnet und schließt die DB, selektiert und gibt aus.

(Bin eigentlich gar nicht da ...)
T.M. schriebAchso, es ging wirklich um Kürze.
Eher um die Vermeidung von Komplexität. Perl-Code beispielsweise kann sehr kurz aber dennoch hochkomplex sein.
Nichts leichter als das. Unter Benutzung von PoCo würde ich dann schreiben:
cout << RecordSet(
            Session("SQLite","sample.db"),
            "SELECT * FROM MyTable"
        );
Ein voll lauffähiger (nicht einmal fiktiver) Einzeiler unter Benutzung zweier unbenannter, temporärer Objekte (RecordSet und Session). Öffnet und schließt die DB, selektiert und gibt aus.
Das sieht schon besser aus, ja. Aber der wichtige Unterschied ist, dass es nach wie vor keinen Kontext etabliert in dem Dein Code läuft und somit die Vorgabe nicht erfüllt. Wie sähe in PoCo folgendes aus:
(with-sql (mysql "...")
  (format t "foo is ~a and bar is ~a~%" (query "...")
                                        (query "..."))
  (format t "enter a query string: ")
  (format t "~a~%" (query (read))))
Es muss nicht genau dieser Code sein, es geht nur darum, Dir zu zeigen worum es bei der Kontext-Geschichte geht. Der Code sendet zwei queries und druckt das Ergebnis, dann fragt er nach einem query-string und druckt das Ergebnis.

Bitte verstehe, dass es hier nicht nur um das geht, was ich in den kleinen Snippets zeige, sondern versuche den Sinn dahinter zu erkennen. Der Sinn ist, dass innerhalb des Kontextes jeglicher Code stehen und ich beliebig viele queries haben kann.

(Zu "nicht einmal fiktiv" wenn ich bestehende Libs verwende, kann ich ebenfalls lauffähige Beispiele zeigen, darum geht es mir nicht. Ich bewege mich nach wie vor im standardisierten Sprachkern und habe lediglich keine Lust, meine Zeit mit der Implementation einer sql-Bibliothek zu vertreiben. (Nicht, dass es was zur Sache täte 🙂 )

ED:
Nein, ich meinte "offenhalten" im Sinne von "offenlassen, welche man konkret benutzt".
Ok, aber dann verstehe ich den Sinn der Aussage nicht ganz, du kannst ja jegliche (sql) DB mit with-sql verwenden, das war ja unter anderem der Sinn der Sache. (with-sql (mysql ...)) (with-sql (postgresql ...)) usw.

Wenn Du meintest, komplett vom Backend zu abstrahieren, sprich überhaupt keinen query-string zu verwenden, sondern eine higher-level API zu schaffen, dann ok, aber das geht über die Aufgabe erstmal hinaus, die ja letztlich immer noch bloß ein Beispiel für Metaprogrammierung und nicht den Anspruch einer Komplettlösung für alle Datenbankbelange darstellen soll.
Das Wort "Kontext" existiert in C++ nicht, weder als Begriff noch als Konzept. Das wollte ich noch hinzufügen. Es handelt sich ja dabei um den Zugriff auf ein implizit erzeugtes, anonymes Objekt. Man bemerkt vielleicht nicht einmal, daß überhaupt ein Objekt im Spiel ist, man rechnet auch nicht mit seiner Konstruktion sowie Destruktion (alles wichtige Dinge!).

Ein wesentliches Problem dabei ist beispielsweise, welches Objekt ist gemeint, wenn sich zwei oder noch mehr "Kontexte" überlappen? (In C++ hat man ein solches Problem mit Namespaces und das ist verdammt lästig!) Was, wenn Du aus einer Datenbank lesen und die Daten in eine andere Datenbank schreiben willst? Dies ist nur über eindeutig benannte Objekte vernünftig zu machen, was ich auch nicht als Zunahme an Komplexität bezeichnen würde. Es ist schlicht ein Gebot der Vernunft.

Ein zweites Problem ist, daß man bei Verwendung solcher Kontexte leicht aus den Augen verliert, was eigentlich der Kontext ist, insbesondere wenn der Kontext mehr als eine Bildschirmseite umfaßt und seine Erstellung gerade nicht sichtbar ist oder auch, und dies ist ein sehr fieser Fall, wenn es sich um fremden Code handelt. Ich weiß dann gar nicht, woraus der Kontext eigentlich besteht, man sieht ihm ja nichts an, nicht einmal einen Typ.
T.M. schriebDas Wort "Kontext" existiert in C++ nicht, weder als Begriff noch als Konzept. Das wollte ich noch hinzufügen. Es handelt sich ja dabei um den Zugriff auf ein implizit erzeugtes, anonymes Objekt. Man bemerkt vielleicht nicht einmal, daß überhaupt ein Objekt im Spiel ist, man rechnet auch nicht mit seiner Konstruktion sowie Destruktion (alles wichtige Dinge!).
Eben weil es nicht existiert und es hier um Metaprogrammierung geht, sollst Du es ja einführen. Was soll bitte der Punkt einer Metaprogrammierung sein, die es nicht erlaubt, neue Konzepte und Konstrukte einzuführen? Was den Begriff Kontext angeht, so ist er lediglich eine passende Beschreibung, nennen kannst Du das es gerne wie Du möchtest.
Ein wesentliches Problem dabei ist beispielsweise, welches Objekt ist gemeint, wenn sich zwei oder noch mehr "Kontexte" überlappen? (In C++ hat man ein solches Problem mit Namespaces und das ist verdammt lästig!) Was, wenn Du aus einer Datenbank lesen und die Daten in eine andere Datenbank schreiben willst? Dies ist nur über eindeutig benannte Objekte vernünftig zu machen, was ich auch nicht als Zunahme an Komplexität bezeichnen würde. Es ist schlicht ein Gebot der Vernunft.
In unserem konkreten Beispiel würde ein Kontext den anderen lexikalisch überlagern, richtig. Variablen gleichen Namens überlagern sich ebenfalls, benutzt Du auch sie deshalb nicht? In anderen Fällen (with-open-file) ist das erstens nicht der Fall, zweitens wäre es kein Problem, ähnlich wie bei with-open-file ein weiteres Argument einzuführen, welches die einzelnen Objekte jeweils referenziert. Problemlos. Bereits in dem Post, in welchem ich with-sql eingeführt habe, habe ich darauf hingewiesen, dass ich keine höheren Design-Überlegungen angestellt, sondern lediglich das geschrieben habe, was der Fragende wissen wollte. Ob man solch ein Makro nun so, oder so implementiert, bleibt jedem selbst überlassen. In eine andere DB das Ergebnis zu schreiben ist übrigens überhaupt kein Problem, da lexikalischer Skopus dies nicht ausschließt – trotzdem bleibt der Einwand im Grunde teilweise berechtigt, was diesen Konkreten Fall angeht. (Zum Beispiel gibts Leute, die awhen/aif (implizit Bindende Konditionale) lieben, andere hassen sie wegen er impliziten Bindung (sie benutzen explizit bindende bif/bwhen). Die Hauptsache ist: Alle bekommen, was sie wollen, weil die Sprache jegliche Abstraktion erlaubt. Metaprogrammierung eben.
Ein zweites Problem ist, daß man bei Verwendung solcher Kontexte leicht aus den Augen verliert, was eigentlich der Kontext ist, insbesondere wenn der Kontext mehr als eine Bildschirmseite umfaßt und seine Erstellung gerade nicht sichtbar ist oder auch, und dies ist ein sehr fieser Fall, wenn es sich um fremden Code handelt. Ich weiß dann gar nicht, woraus der Kontext eigentlich besteht, man sieht ihm ja nichts an, nicht einmal einen Typ.
Wenn man Funktionen, Makros, oder welcherlei Blöcke auch immer, auf über eine Bildschirmseite ausdehnt, hat man ohnehin schon ein Problem und nicht vernünftig abstrahiert, ganz einfach. Dass dies in Java und C++ üblich ist, sagt schon einiges über diese Sprachen aus.

Des Weiteren widersprechen ungefähr 50(!) Jahre Lisp-Geschichte Deinen Theorien. Selbst Python hat mit seinem with-Statement einen verkrüppelten Abklatsch dieser "Kontexte" eingeführt. Ganz im Gegenteil zu Deinen Annahmen erleichtern Makros das Lesen fremden Codes und die Verwendung enorm. Eine Funktion abstrahiert ebenfalls und Du siehst ihr von Außen nicht an, was sie tut, hast Du da auch die angesprochenen Probleme? Was Typen angeht: Lisp ist eine streng dynamisch getypte Sprache, das hat mit der Diskussion hier überhaupt nichts zu tun.

Also, bei aller Kritik, die Du hier einbringst (selbst wenn sie Substanz hätte, was ich bestreite): Wo liegt bitte das Problem dieses Konzept in C++ einzuführen, wenn es, wie Du sagst, Metaprogrammierung erlaubt und formbar ist? Genau darum dreht sich unsere ganze Diskussion.

Konkret: Kritik hin oder her, bist Du in der Lage das gewünschte Konstrukt mit Hilfe der C++ eigenen Mittel zu implementieren, oder nicht?

Da Dir auch dieses Beispiel nicht gefallen hat, liefere ich Dir gerne noch ein weiteres (und beliebig viele weitere, so es denn sein muss):

Wir haben keine Lust mehr, immer wenn wir etwas n mal wiederholen wollen for(x = 0; x < n; x++) { <code>;* } zu schreiben, also möchten wir, als fähige Metaprogrammierer und Möchtegern-Sprachdesigner dieses Konzept abstrahieren. Wir führen ein syntaktisches Konstrukt repeat ein, welches den Boilerplate für uns übernimmt, und uns vor off-by-one Fehlern bewahrt.
(defmacro repeat (n &body body)
  `(dotimes (_ ,n)
     ,@body))
Aufgerufen wird es folgendermaßen:
(repeat 5
  (print 'hallo)
  (print 'blabla))
Also, wie macht man das in C++?

(Anm.: Natürlich war das jetzt etwas zu einfach, da es ein, im Prinzip gleichwertiges, Konstrukt schon im Sprachkern gibt. Solltest Du eine Lösung sehen wollen, die von anderen Konstrukten abstrahiert, welche ähnlich zu C/C++s for aufgebaut sind, kann ich auch das gerne liefern.)

Noch ein Beispiel:

Wir möchten (bitte keine Diskussion, ob man das braucht, es geht darum zu zeigen, wie man neue Syntax schafft) eine Syntax erstellen, die alles innerhalb eckiger Klammern als Liste von Zahlen betrachtet und die Summe aller Elemente zurückliefert.
(defreadtable sum-bracket-syntax
  (:merge :standard)
  (:macro-char #\] (get-macro-character #\)))
  (:macro-char #\[ #'(lambda (stream char)
                       (reduce #'+ (read-delimited-list #\] stream)))))

(in-readtable sum-bracket-syntax)
(Anm: Ich benutze hier benannte Readtables, die nicht im Sprachstandard definiert sind. Gleiches ginge mit Standardmitteln, sprich normalen Readtables, aber ich betrachte diese aus modularen Gründen als die bessere Lösung)
DHL> [1 2 3 4 5 6 7 8 9 10]
55
Fertig. Nun magst Du anführen, dass [] in C++ schon belegt ist, aber eine Sprache, die zur Metaprogrammierung geeignet ist, hat mit so etwas keine Probleme. Sollte es doch Probleme dabei geben, kannst Du auch gerne andere Zeichen verwenden, sagen wir >> und <<.

Also, ich habe genug Beispiele gezeigt, wie eine Sprache die sich als geeinet für Metaprogrammierung versteht funktioniert und ich habe an Beispielen den Sinn dieser Möglichkeiten erläutert. Natürlich hast Du Bedenken und (teilweise berechtigte, teilweise durch Unkenntnis entstandene) Einwände, das ist auch ok. Trotzdem: Du hast nicht eine einzige der gestellten Aufgaben gelöst, obwohl Du ein fähiger Programmierer bist. Mir persönlich sagt das: C++ ist zur Metaprogrammierung gänzlich ungeeignet und ich kann mir auch die nächsten zehn Jahre die Zeit sparen, mich damit zu beschäftigen. Gerne hätte ich mich auch vom Gegenteil überzeugen lassen. Gleiches gilt übrigens für das OOP-Beispiel.
Danke für den Thread. Er hat mich bestärkt, endlich mal Programmieren zu lernen. Als ungeduldiger Mathe-Unbegabter mit einem fürchterlichen Gedächtnis wird das vermutlich ein harter Kampf und ich hoffe, dass ich genug Durchhaltevermögen an den Tag legen kann. Aber Irgendwann nervt's einfach, es nicht zu können. Nach gründlicher Recherche hab ich mich jedenfalls für Python entschieden.
dade schriebDanke für den Thread. Er hat mich bestärkt, endlich mal Programmieren zu lernen. Als ungeduldiger Mathe-Unbegabter mit einem fürchterlichen Gedächtnis wird das vermutlich ein harter Kampf und ich hoffe, dass ich genug Durchhaltevermögen an den Tag legen kann. Aber Irgendwann nervt's einfach, es nicht zu können. Nach gründlicher Recherche hab ich mich jedenfalls für Python entschieden.
Ich denke, dass Du eine gute Wahl getroffen hast. Große Community, ausreichend Libs, ausdrucksmächtig und nicht unnötig komplizierend. Eine gute Sprache, sowohl um in die Programmierung einzusteigen, als auch seine Arbeit schnell erledigt zu bekommen. Viel Spaß!
T.M. schriebEin wesentliches Problem dabei ist beispielsweise, welches Objekt ist gemeint, wenn sich zwei oder noch mehr "Kontexte" überlappen?
danlei schriebZum Beispiel gibts Leute, die awhen/aif (implizit Bindende Konditionale) lieben, andere hassen sie
Eigentlich kann man genau das als gutes Beispiel anführen.

Wenn man in einem Konditional den Antezedens in der Konsequenz verwenden möchte, sieht das normalerweise so aus:
(if (look-for-foo)
  (print (look-for-foo))
  (print "sorry, there is no foo."))
Wenn und nur wenn eine Funktion look-for-foo etwas zurückgibt (in Lisp heißt das, alles was nicht nil bzw. nichts, eine leere Liste oder false ist) soll dieses Ergebnis gedruckt werden. Problem ist natürlich, das hier (look-for-foo), welches Seiteneffekte haben könnte, oder aufwändig ist, zweimal aufgerufen wird. Klar, man sollte das Ergebnis, welches ja zweimal benötigt wird, binden:
(let ((a-foo? (look-for-foo)))
  (if a-foo?
    (print a-foo?)
    (print "sorry, there is no foo.")))
Da dieses Muster nun relativ häufig auftritt und eigentlich nur Boilerplate ist, gibt es Leute (darunter ich), die sich mit entsprechenden Abstraktionen behelfen:
(defmacro aif (expression then &optional else)
  `(let ((it ,expression))
     (if it ,then ,else)))

(defmacro bif ((var expression) then &optional else)
  `(let ((,var ,expression))
     (if ,var ,then ,else)))
Das Prefix a steht hier für "anaphoric", das b für "binding". Das obige Beispiel sähe dann so aus:
(aif (look-for-foo)
  (print it)
  (print "no foo today."))

(bif (a-foo? (look-for-foo)
  (print a-foo?)
  (print "ain't got foo."))
Diese beiden Variationen sind gleichzeitig ein Beispiel für das von Dir angeführte eventuelle Problem des "variable capture", als auch die Lösung, denn bif ermöglicht Verschachtelung:
(bwhen (foo (make-foo))
  (bwhen (bar (make-bar))
    (format t "got ~a and ~a~%" foo bar)))


aif hat das mögliche Problem, welches Du korrekt erkannt hast, bif nicht.

Trotzdem benutze ich meist aif und ich kann Dir versichern, da gibts in der Praxis keine Probleme. Natürlich kann man sich darüber Streiten, ob diese Konstrukte guter Stil sind, oder nicht, aber stelle dir vor, der Antezedens sei sehr komplex und es wäre nicht zwingend nötig, ihn zu binden. Da schafft aif, meines Erachtens, eher Klarheit und Kürze und ist eine gute Sache.

Natürlich sollte man die Kirche im Dorf lassen und es mit solcherlei Abstraktion nicht übertreiben, aber Dinge wie aif/bif/awhen/bwhen sind erfahrenen Lisp-Programmierern geläufig und selbst, wenn sie deren Anwendung nicht befürworten, können sie den Code dennoch gut lesen und verstehen.

Des Weiteren steht hinter jedem Makro eine Funktion, die von diesem syntaktisch gezuckert wird. Man benutzt so etwas nicht um seiner selbst willen, sondern belässt es bei Funktionen, wo Funktionen die gewünchte Funktionalität bereitstellen. Über einer Funktionsschicht liegt in der Praxis oft eine Makroschicht, welche die API vereinfacht, so siehts in der Praxis aus.

Aber ich glaube, ich gehe schon zu sehr ins Detail.
danlei schriebDes Weiteren widersprechen ungefähr 50(!) Jahre Lisp-Geschichte Deinen Theorien.
Die Tatsache, dass etwas irgendwo geht und anderswo anders bewertet wird, widerspricht gar nichts. Es ist schlicht Vielfalt. Und die Vielfalt ist stets ein Gewinn, egal von welcher Seite aus man sie betrachtet.
danlei schriebSelbst Python hat mit seinem with-Statement einen verkrüppelten Abklatsch dieser "Kontexte" eingeführt.
Ich kannte ein with-Schlüsselwort aus TurboPascal. Ich hab es, ehrlich gesagt, dort nie verwendet und es hat mir seitdem auch nie gefehlt. Nicht ein einziges Mal.
danlei schriebGanz im Gegenteil zu Deinen Annahmen erleichtern Makros das Lesen fremden Codes und die Verwendung enorm. Eine Funktion abstrahiert ebenfalls und Du siehst ihr von Außen nicht an, was sie tut, hast Du da auch die angesprochenen Probleme?
Selbstverständlich, sofern sie nicht vernünftig dokumentiert ist! Und eine Funktion hat idealerweise wenigstens einen sprechenden Namen und ein Signatur, die alle beteiligten Typen klar bezeichnet. Das halte ich für wesentliche Informationen. In C++ kommen noch mögliche Exceptions sowie weitere Attribute (const, static, virtual usw.) hinzu, die das Verhalten der Funktion/Methode nach aussen hin möglichst vielsagend kennzeichnen. Und genau weil all das in klassischen C-Makros verlorengeht, löst man in C++ solche Metaprogrammierung zunehmend mit streng typisierten Funktionstemplates.
danlei schriebWas Typen angeht: Lisp ist eine streng dynamisch getypte Sprache, das hat mit der Diskussion hier überhaupt nichts zu tun.
Doch, selbstverständlich. Deinem with-irgendwas sieht man nämlich den Typ nicht an! Es ist generisch. Es hängt von Bedingungen ab, was für einen Typ das unbenannte Objekt bekommt, auf dem man dann arbeitet. In TurboPascal musste man das im with verwendete Objekt wenigstens vorher definieren, damit es bekannt war.
danlei schriebAlso, bei aller Kritik, die Du hier einbringst (selbst wenn sie Substanz hätte, was ich bestreite): Wo liegt bitte das Problem dieses Konzept in C++ einzuführen, wenn es, wie Du sagst, Metaprogrammierung erlaubt und formbar ist? Genau darum dreht sich unsere ganze Diskussion.

Konkret: Kritik hin oder her, bist Du in der Lage das gewünschte Konstrukt mit Hilfe der C++ eigenen Mittel zu implementieren, oder nicht?
Nein, nicht auf Anhieb. Wie gesagt, das Sprachmittel eines Kontextes fehlt in C++ grundsätzlich, genau wie es in anderen Sprachen keine Destruktoren oder Mehrfachvererbung oder templates gibt. Wie wollte man sowas dann nachbilden? Aber so ein Kontext ist schlicht nicht DIE Metaprogrammierung, auch nicht ein wesentliches Element einer solchen, es ist schlicht nicht notwendig. Es gibt zahlreiche andere und wichtigere Dinge auf Metaebene, die ich sehr wohl erfolgreich implementieren kann. C++ bietet dazu eine historisch gewachsene Palette an Mitteln an. Und das macht es am Ende zu einer durchaus brauchbaren und bequemen (!) Sprache.
danlei schriebDa Dir auch dieses Beispiel nicht gefallen hat, liefere ich Dir gerne noch ein weiteres (und beliebig viele weitere, so es denn sein muss):
Nein, muss es nicht. Es ist schlicht nicht wichtig.
T.M. schriebDie Tatsache, dass etwas irgendwo geht und anderswo anders bewertet wird, widerspricht gar nichts. Es ist schlicht Vielfalt. Und die Vielfalt ist stets ein Gewinn, egal von welcher Seite aus man sie betrachtet.
Schön gesagt, allerdings bevorzuge ich dann dementsprechend Sprachen, die eine möglichst große Vielfalt bieten und das schließt Metaprogrammierung (richtite, keinen Abklatsch) mit ein.
Ich kannte ein with-Schlüsselwort aus TurboPascal. Ich hab es, ehrlich gesagt, dort nie verwendet und es hat mir seitdem auch nie gefehlt. Nicht ein einziges Mal.
Ich kann mich zwar an dieses Statement nicht erinnern, aber ich habe Pascal auch nie sonderlich gemocht. Sollte es wirklich die verlangte Funktionalität bieten, so kann es durchaus sein, dass sich aufgrund anderer Gründe (zum Beispiel eben der anderen "Kultur") dort nicht etabliert hat.
Selbstverständlich, sofern sie nicht vernünftig dokumentiert ist! Und eine Funktion hat idealerweise wenigstens einen sprechenden Namen und ein Signatur, die alle beteiligten Typen klar bezeichnet. Das halte ich für wesentliche Informationen. In C++ kommen noch mögliche Exceptions sowie weitere Attribute (const, static, virtual usw.) hinzu, die das Verhalten der Funktion/Methode nach aussen hin möglichst vielsagend kennzeichnen. Und genau weil all das in klassischen C-Makros verlorengeht, löst man in C++ solche Metaprogrammierung zunehmend mit streng typisierten Funktionstemplates.
Warum sollte ein Makro einen weniger sprechenden Namen als eine Funktion haben müssen, oder schlechter Dokumentiert sein? Das ist alles vollkommen orthogonal dazu, ob man nun ein Makro, oder eine Funktion verwendet.

Zum Thema "man löst solche Metaprogrammierung": Das was Du ansprichst, Templates, ist mitnichten wirkliche Metaprogrammierung, es ist lediglich eine spezielle Anwendung derselben und sie löst im Falle von C++ einfach nur ein Problem statisch getypter Sprachen.
Doch, selbstverständlich. Deinem with-irgendwas sieht man nämlich den Typ nicht an! Es ist generisch. Es hängt von Bedingungen ab, was für einen Typ das unbenannte Objekt bekommt, auf dem man dann arbeitet. In TurboPascal musste man das im with verwendete Objekt wenigstens vorher definieren, damit es bekannt war.
Das läuft, um es noch einmal zu betonen, einzig und allein auf den Unterschied dynamisch und statisch getypter Sprachen hin.
(defmacro foo (bar)
   ...)

(defun foo (bar)
   ...)
So sehen in Lisp nunmal Funktionen und Makros aus, so lange man keinen Typ angibt, was optional ist. Auch dynamische vs. statische Typisierung ist ein Streitpunkt, aber ein anderer.
Nein, nicht auf Anhieb. Wie gesagt, das Sprachmittel eines Kontextes fehlt in C++ grundsätzlich, genau wie es in anderen Sprachen keine Destruktoren oder Mehrfachvererbung oder templates gibt. Wie wollte man sowas dann nachbilden? Aber so ein Kontext ist schlicht nicht DIE Metaprogrammierung, auch nicht ein wesentliches Element einer solchen, es ist schlicht nicht notwendig. Es gibt zahlreiche andere und wichtigere Dinge auf Metaebene, die ich sehr wohl erfolgreich implementieren kann. C++ bietet dazu eine historisch gewachsene Palette an Mitteln an. Und das macht es am Ende zu einer durchaus brauchbaren und bequemen (!) Sprache.
Noch einmal: Dieses Sprachmittel muss es nicht geben, es soll geschaffen werden. Das scheint für Dich unglaublich schwer zu verstehen zu sein. Dieses System, das weit über die Etablierung von Kontexten hinaus geht, erlaubt es mir alles in die Sprache einzuführen, das sollte aus allem, was ich gezeigt habe, klar geworden sein. Es gibt andere Dinge, die Du implementieren kannst? Die letzten beiden Beispiele (die ich gerne in C++ gesehen hätte) hast Du einfach geflissenlich ignoriert. Warum? Weil C++ einfach nicht zu so etwas in der Lage ist. Es geht nicht darum, was die Sprache bietet, sondern darum, ob sie es erlaubt, mit Sprachmitteln erweitert zu werden.

Alles was Du ansprichst, ob Du es glaubst oder nicht, kann mit Makros in Lisp eingeführt werden, inklusive statischer Typisierung, Conditions(Exceptions), oder was auch immer Dir einfällt. (Minimale Einschränkungen gibt es beispielsweise bei Continuations, aber auch das geht weitestgehend mit Sprachmitteln und es wurde auch schon ein Ansatz gezeigt, volle Continuations allein auf Makrobasis einzuführen.

Um einen letzten verzweifelten Versuch zu starten: Lisp benutzt eine Prefix-Syntax. Infix-Syntax ist in der Sprache nicht vorhanden, aber sie gibt mir die Möglichkeit dies einzuführen, wenn es sein muss inklusive C-Ähnlicher Syntax, ohne Probleme:
(defun infix-to-prefix (form)
  (labels ((expand-argument (form)
             (if (listp form)
                 (infix-to-prefix form)
                 form)))
    (if (null (cdr form))
        (expand-argument (car form))
        (destructuring-bind (arg op . rest) form
          (list op (expand-argument arg)
                (when rest (infix-to-prefix rest)))))))

(defreadtable i-like-c
  (:merge :standard)
  (:macro-char #\; (get-macro-character #\)))
  (:dispatch-macro-char #\# #\c
    (lambda (stream char n)
      (infix-to-prefix (read-delimited-list #\; stream)))))
Das ganze Funktioniert so:
DHL> (infix-to-prefix '(1 + 2 + 3 * 4))
(+ 1 (+ 2 (* 3 4)))
DHL> (infix-to-prefix '((1 / 2) + (3 / 4)))
(+ (/ 1 2) (/ 3 4))
Das ist die Funktionsschicht, aber das sieht noch viel zu sehr nach Lisp aus, als dass es uns gefallen würde:
DHL> (in-readtable i-like-c)
(("DHL" . #<NAMED-READTABLE I-LIKE-C #x8EBDD36>))
DHL> #c 1 + 2 + 3;
6
DHL> #c 5 expt 2;
25
DHL> #c (3 * 10) + (24 / 2);
42
Natürlich habe ich hier abgekürzt und den Infix-Prefix-Compiler so einfach wie es nur ging gehalten (sprich: ohne Operatoren-Rangfolge einzubaun), aber ich habe innerhalb nicht einer Seite Codes die Sprache um ein völlig neues Konzept erweitert. Aus einer reinen Prefix-Sprache ist, ohne Aufwand, eine Sprache geworden, die auch Infix beherrscht. Ich kann meine Sprache zu allem machen, was ich will. Das ist wahre Metaprogramierung.
Nein, muss es nicht. Es ist schlicht nicht wichtig.
An die Tür des Tauben klopf, so oft Du willst.
Je länger ich das mitlese um so mehr bin ich überzeugt, dass ihr aneinander vorbei redet. Für mich bietet sich der Stand so dar (korrigiert mich, wenn ich hier falsch liege):

Im Kern ist danleis dynamische Sicht auf die Problemlösung bedeutend weiter gefasst als T.M.s statisch fundierte Erfahrungswelt. Es macht schon einen Unterschied, ob man primär in Kontexten oder in Objekten und Objektumgebungen denkt.

Letzteres ist meiner Erfahrung nach, immer die nötige Disziplin vorausgesetzt, sinnvoller bei größeren Projekten, bei denen viele mit unterschiedlichen Erfahrungen (und unterschiedlichem Können) zusammenarbeiten müssen, denn so lassen überprüfbare Regeln bedeutend leichter aufstellen und überwachen. Im Kern ist es dies, Konstruktion nach festen Regeln und längerfristige Wartbarkeit, wovon T.M. meines Erachtens spricht.

Die dynamischen Möglichkeiten zur Problemlösung, wie sie Lisp bietet, machen es demgegenüber viel einfacher, elegante, dabei aber durchaus zuverlässige Lösungen sozusagen ad-hoc zu gestalten, wobei der Anwendungsbereich sehr viel breiter ist. (Schließlich sind viele Konzepte statischer Programmiersprachen und -techniken zuerst in Lisp erprobt worden.)

Ich für meinen Teil bevorzuge eigentlich Lisp. Leider sind (waren ?) Lisp-Lösungen nur schwierig in die derzeit üblichen Systemumgebungen einzubinden. (Es geht wohl, aber ich muss mich da erst wieder neu hineinfinden - meine Lisp-Erfahrungen sind auch schon wieder eine Dekade alt. 🙁 ) Also bleibt praktisch nur C, C++, Python etc.

Schade eigentlich. Immerhin habe ich mich jetzt aufgerafft, meine Lisp-Kenntnisse wieder aufzufrischen und zu versuchen, sie an die neuesten Entwicklungen anzugleichen. (Auch ein Abenteuer...)
Letzteres ist meiner Erfahrung nach, immer die nötige Disziplin vorausgesetzt, sinnvoller bei größeren Projekten, bei denen viele mit unterschiedlichen Erfahrungen (und unterschiedlichem Können) zusammenarbeiten müssen, denn so lassen überprüfbare Regeln bedeutend leichter aufstellen und überwachen. Im Kern ist es dies, Konstruktion nach festen Regeln und längerfristige Wartbarkeit, wovon T.M. meines Erachtens spricht.
Die Vorstellung, dass man die dummen Programmierer vor sich selbst schützen müsse führte uns zu Java. Na toll. 🙂

Spaß beiseite: Lisp ist durchaus in großen Projekten verwendet worden und wird es auch heute noch. Siehe z.B. http://www.itasoftware.com/careers/l_e_t_lisp.html. Ich denke nicht, dass solche Firmen auf Lösungen setzen würden (so eine Airline-Software ist schon ein ziemlich großes Projekt), wenn wirklich Probleme beim skalieren zu erwaren wären. Weiters ist Common Lisp auf druck der ARPA standardisiert worden, da das Militär den entsprechenden Bedarf hatte. Auch diese Leute legen Wert auf Sicherheit und Wartbarkeit (hoffe ich zumindest 🙂 )
Im Kern ist danleis dynamische Sicht auf die Problemlösung bedeutend weiter gefasst als T.M.s statisch fundierte Erfahrungswelt. Es macht schon einen Unterschied, ob man primär in Kontexten oder in Objekten und Objektumgebungen denkt.
Wichtig ist vor allem, dass das, was ich "Kontexte" nenne nur ein kleiner Teil dieser Erweiterungsfähigkeit sind. Man kann alles schaffen, was man will, das ist der Punkt. Es ist auch in Lisp kein "entweder, oder". Unser Objektsystem CLOS (welches ebenfalls in Makros über einer Funktionsschicht implementiert ist) ist dem von C++ mit Sicherheit nicht unterlegen, eher im Gegenteil. Man kann all diese Dinge auch objektorientiert Lösen, keine Frage.
Die dynamischen Möglichkeiten zur Problemlösung, wie sie Lisp bietet, machen es demgegenüber viel einfacher, elegante, dabei aber durchaus zuverlässige Lösungen sozusagen ad-hoc zu gestalten, wobei der Anwendungsbereich sehr viel breiter ist. (Schließlich sind viele Konzepte statischer Programmiersprachen und -techniken zuerst in Lisp erprobt worden.)

Ich für meinen Teil bevorzuge eigentlich Lisp. Leider sind (waren ?) Lisp-Lösungen nur schwierig in die derzeit üblichen Systemumgebungen einzubinden. (Es geht wohl, aber ich muss mich da erst wieder neu hineinfinden - meine Lisp-Erfahrungen sind auch schon wieder eine Dekade alt. 🙁 ) Also bleibt praktisch nur C, C++, Python etc.
Es könnte besser sein, aber es hat sich auch schon gebessert. Natürlich wäre es nett, eine Community wie Python oder Perl zu haben, wo tausende von Code-Monkeys Bibliotheken zu allem Möglichen produzieren, keine Frage, aber im Zweifelsfall lege ich mehr Wert auf Erweiterbarkeit und Flexibilität im Sprachkern, ja.
Schade eigentlich. Immerhin habe ich mich jetzt aufgerafft, meine Lisp-Kenntnisse wieder aufzufrischen und zu versuchen, sie an die neuesten Entwicklungen anzugleichen. (Auch ein Abenteuer...)
Neben den weiter oben aufgeführten Ressourcen kannst Du Dir auch z.B. www.cliki.net, oder common-lisp.net ansehen. Hast Du ein konkretes Problem?

Allgemein: Ich sage ja nicht, dass man C++ "ausrotten" sollte, oder sowas. Ich bestreite nur, dass es zu wirklicher Metaprogrammierung in der Lage ist und bin nach wie vor der Ansicht, dass es nur unzulängliche Abstraktionsmöglichkeiten bietet, beziehungsweise diese höher komplex als nötig sind.
bernarcher schriebJe länger ich das mitlese um so mehr bin ich überzeugt, dass ihr aneinander vorbei redet. Für mich bietet sich der Stand so dar (korrigiert mich, wenn ich hier falsch liege):

Im Kern ist danleis dynamische Sicht auf die Problemlösung bedeutend weiter gefasst als T.M.s statisch fundierte Erfahrungswelt. Es macht schon einen Unterschied, ob man primär in Kontexten oder in Objekten und Objektumgebungen denkt.
Der Unterschied in der Argumentation liegt noch woanders. danlei hat sich meiner Ansicht nach mit einiger Vehemenz auf einem Nebenthema (!), nämlich der sog. Metaprogrammierung, festgebissen - ich weiss eigentlich bis jetzt nicht, warum - genau diese ist mir eigentlich recht gleichgültig. Ich betrachte aus meiner Erfahrung völlig andere Dinge als wesentlich, ein paar davon hab ich auch genannt, insbesondere nämlich inhaltliche Themen und das daraus abgeleitete Design, Wartbarkeit, Erweiterbarkeit, Wiederverwendbarkeit, Portabilität, dort geht bei einem erfahrenen Programmierer die Zeit verloren! Aber so gesehen steht C++ gemessen an anderen Sprachen gar nicht schlecht da. Das geht hier völlig unter.

Ich habe in meinem Leben bislang jede (!) Aufgabe programmatisch lösen können, darunter beispielsweise (bevor Java bekannt wurde) ein vollständiger Compiler für eine objektorientierte Sprache mit Polymorphie, Mehrfachvererbung, frei (!) definierbaren Operatoren, exception handling u.a.m., und ich halte das, was ich dabei zustandegebracht habe, durchaus nicht für unelegant, schon gar nicht wegen C++. Ich bin noch nie an eine solche Grenze gestossen, dass ich hätte sagen müssen, moment mal, so geht's nicht weiter, wir brauchen ein anderes Werkzeug. Klingt alles sehr nach Unbrauchbarkeit, gell?

Wir waren ursprünglich bei Programmiersprachen und danlei nannte so gut wie alles eine solche, nur nicht C++, das war (komischerweise trotz Java) seine einzige Ausnahme, und zwar (andere Argumente hab ich bislang nicht gehört) wegen des Fehlens solcher Metaprogrammierungsmöglichkeiten. Dies halte ich nicht nur für ein bisschen polemisch, sondern schlicht für albern, nämlich vor allem im Hinblick auf den Anteil den C++ seit vielen Jahren an der Entwicklung hat und auch wohl eine ganze Zeitlang noch haben wird.

Ich hab, ehrlich gesagt, keine grosse Lust mehr, diese Diskussion länger fortzusetzen. Die Argumente haben sich erschöpft.
T.M. schriebDer Unterschied in der Argumentation liegt noch woanders. danlei hat sich meiner Ansicht nach mit einiger Vehemenz auf einem Nebenthema (!), nämlich der sog. Metaprogrammierung, festgebissen - ich weiss eigentlich bis jetzt nicht, warum - genau diese ist mir eigentlich recht gleichgültig.
Warum? Weil sie komplexe Dinge einfacher, übersichtlicher und leichter wartbar macht. Ist Dir schon in den Sinn gekommen, dass sie für Dich ein Nebenthema ist, weil Deine Sprache sie (bis auf einen eng gefassten Ausschnitt) quasi unmöglich macht und dass sie, in Sprachen, welche sie ermöglichen, ein probates Mittel zur Optimierung der Sprache auf die eigenen Anforderungen hin ist?

Davon abgesehen ging es ja gerade darum, dass ich kritisierte, dass C++ nicht ausreichend mächtige Abstraktionsmöglichkeiten bietet, die jedem (nicht nur den Sprachdesignern, die nie wissen können, was Du brauchst) es ermöglichen, fehlende Abstraktionen wirklich nahtlos in die Sprache einzufügen und dass aufgrund dessen die Sprache im Kern immer komplexer gemacht werden muss. So sind wir zur Metaprogrammierung gekommen, die dieses Problem löst. Nicht möglichst viele Features, sondern möglichst mächtige Features, darum ging es mir ursprünglich.

Keine Sprache ist für jedes Problem gleich gut geeignet, da sind wir uns, glaub ich, einig. Wenn man nun ein konkretes Problem lösen will, was läge näher, als eine Sprache zu benutzen, die auf das Problem zugeschnitten und optimal für seine Lösung geeignet ist? Genau das kann man in Lisp machen: Man formt die Sprache auf das Problem hin und löst dieses dann in der für seine Lösung hin ausgelegten Sprache.
Ich betrachte aus meiner Erfahrung völlig andere Dinge als wesentlich, ein paar davon hab ich auch genannt, insbesondere nämlich inhaltliche Themen und das daraus abgeleitete Design, Wartbarkeit, Erweiterbarkeit, Wiederverwendbarkeit, Portabilität, dort geht bei einem erfahrenen Programmierer die Zeit verloren! Aber so gesehen steht C++ gemessen an anderen Sprachen gar nicht schlecht da. Das geht hier völlig unter.
Dass C++ im Vergleich zu manchen Sprachen gut dasteht heißt nicht, dass es nicht im Vergleich zu anderen schlecht dasteht.

Was ich aber auch, um Dir ein Stück weit entgegenzukommen, sagen muss: Im Rahmen der Diskussion habe ich mir ein wenig über modernes C++ angelesen und bis auf die fehlende Speicherbereinigung, die man mit Boost lösen kann, steht es nicht schlechter da als Java (wobei ich schon sagen würde, dass es komplexer ist). So viel sei zugestanden.
Ich habe in meinem Leben bislang jede (!) Aufgabe programmatisch lösen können,
Bis auf jede einzelne, die ich Dir gestellt habe. (Was nicht zwingend heißt, dass es unmöglich wäre, aber ich behaupte, bis Du mir das Gegenteil beweist, dass die Umsetzungen in C++ hochkomplex und somit, für einen C++-Programmierer, unsinnig sind.)
darunter beispielsweise (bevor Java bekannt wurde) ein vollständiger Compiler für eine objektorientierte Sprache mit Polymorphie, Mehrfachvererbung, frei (!) definierbaren Operatoren, exception handling u.a.m., und ich halte das, was ich dabei zustandegebracht habe, durchaus nicht für unelegant, schon gar nicht wegen C++. Ich bin noch nie an eine solche Grenze gestossen, dass ich hätte sagen müssen, moment mal, so geht's nicht weiter, wir brauchen ein anderes Werkzeug. Klingt alles sehr nach Unbrauchbarkeit, gell?
Dass es möglich ist, steht fest (Turing). Genauso wäre es in Assembler oder blankem Maschinencode möglich gewesen. Die Frage ist, wie einfach es Dir gemacht wird.

Davon abgesehen bietet Dir Lisp die Möglichkeit, alles genannte direkt in die Sprache einzubetten. Es ist schlicht nicht nötig, eine davon getrennte Sprache zu implementieren (was natürlich auch möglich wäre), weil man all das direkt nutzen kann.

Aber um dennoch etwas gutes über C++ zu sagen: Zur systemnahen Programmierung mit relativ hochsprachlichen Mitteln scheint es geeignet zu sein.
Wir waren ursprünglich bei Programmiersprachen und danlei nannte so gut wie alles eine solche, nur nicht C++, das war (komischerweise trotz Java) seine einzige Ausnahme, und zwar (andere Argumente hab ich bislang nicht gehört) wegen des Fehlens solcher Metaprogrammierungsmöglichkeiten. Dies halte ich nicht nur für ein bisschen polemisch, sondern schlicht für albern, nämlich vor allem im Hinblick auf den Anteil den C++ seit vielen Jahren an der Entwicklung hat und auch wohl eine ganze Zeitlang noch haben wird.
Dass ich im ersten Artikel speziell C++ angegriffen habe war schlicht Polemik, ein Spaß auf Kosten schwächerer, wenn Du so willst. Es hätte auch Java treffen können, da hast Du Recht.

Aber im Ernst: Wenn Du Argumente von Metaprogrammierung abgesehen hören wolltest, hättest Du ja gerne das OOP-Beispiel implementieren können und C++s Nachteile auch in diesem Bereich hätten sich offenbart.

Dass C++ überall benutzt wird, macht es nicht zwangsläufig zu etwas gutem, ebenso wurde viel COBOL geschrieben. Macht es das zu einem guten(tm) Werkzeug? Drei-Felder-Wirtschaft? Pfeil und Bogen?
Ich hab, ehrlich gesagt, keine grosse Lust mehr, diese Diskussion länger fortzusetzen. Die Argumente haben sich erschöpft.
Meine nicht, aber ohne Code, zum Beispiel zur OOP-Aufgabe, kann ich nicht viel sagen.
  • [gelöscht]

danlei schriebUm bei dem Snippet zu bleiben:
ifstream i("Dateiname", ios::in);
string line;
try {
------------------------------
    while (getline(i, line))
    {
        cout << line << endl;
    }
------------------------------
} finally {   // gibts finally in c++?
    close(i); // oder wie immer es heißen mag
}
Alles ober- und unterhalb beziehungsweise (zuzüglich des Schliessen des FD, selbstverständlich abgesichert gegen Sprünge, bzw. Exceptions) unterhalb der Linien ist nicht Deine Aufgabe, sondern Aufgabe Deines syntaktischen Konstruktes. Wird in C++-Kreisen wirklich immer noch dieser ganze Boilerplate geschrieben, wenn man Code unter einem geöffneten Filedescriptor laufen lassen will? Das wäre mir zu blöd.
Du möchtest Bekanntschaft mit RAII machen:
http://de.wikipedia.org/wiki/RAII
Und wie löst jetzt RAII das Problem? Das "Close" wird nicht nur aufgerufen, wenn der Stack auf dem das Objekt liegt verlassen wird, sondern eben auch im Fehlerfall beim Einlesen.

Das heißt, das "close" darf eigentlich nicht im Deskruktor stehen, weil des dann unter Umständen zweimal aufgerufen wird; was bei einem "close" jetzt nicht so wild ist, aber dennoch vermieden werden sollte. Womit RAII eben komplett ausfällt.

(Es denn natürlich man schreibt ein bedingtes close; speichert sich also, ob Close schon aufgerufen wurde. Finde ich persönlich nicht soo elegante. Na ja)
Obiger Code ist schlicht nicht richtig. Der Code, der tatsächlich läuft, sieht so aus:
{
   ifstream i("Dateiname", ios::in);
   string line;
   while (i.good() && getline(i, line))
   {
      cout << line << endl;
   }

   // ...

} // <-- [1]
Kein exception handling, kein close oder sonstwas. In diesem Fall wohlgemerkt, und zwar deshalb, weil die stream-Klassen keine exceptions werfen.

Im Fall, dass beispielsweise die Datei nicht existiert oder nicht geöffnet werden kann, wird der Aufruf von i.good() das stream-Objekt in einen Fehlerzustand versetzen, woraufhin das while fehlschlägt. Am Ende des Blocks [1] wird das stream-Objekt korrekt gelöscht, wobei dessen Destruktor genau dann, wenn die Datei geöffnet werden konnte, diese auch wieder schliesst.

Im Fall, dass man exceptions behandelt, ist es ähnlich:
try
{
   AnyObject obj("Hugo");
   throw Exception();

   // ...

} // <-- [2]
catch (const Exception& x)
{
   // ...
}
Das Objekt wird beim Verlassen des try-Blockes [2] destruiert und gelöscht, ganz gleich, ob dies durch exceptions oder regulär erfolgt. Will man im catch-Block noch auf das Objekt zugreifen, so muss man es entweder ausserhalb anlegen, also weiter oben, vor dem try, oder es dynamisch erzeugen und dem Konstruktor der exception übergeben, so dass der Zeiger mitgeworfen wird.
RAII schriebDu möchtest Bekanntschaft mit RAII machen:
http://de.wikipedia.org/wiki/RAII
Erstens löst das, wenn ich die Beschreibung richtig verstanden habe, nur einen Teil der Aufgabe, genauso wie T.M. sie nicht komplett zu lösen vermochte, zweitens: Was hier absolut nicht verstanden wird ist, dass genau das wieder auf eine Extrawurst rausläuft. "Entwurfsmuster" sind ein Zeichen für fehlende Abstraktion auf Sprachebene. Wozu sollte man ein Entwurfsmuster brauchen, wenn die Sprache entsprechende Konstrukte zur Verfügung stelt? Drei Lösungsansätze: 1. Man vertreibt sich die Zeit mit Entwurfsmustern. 2. Die Sprache wird nach und nach mit allen möglichen Features erweitert. 3. Man erlaubt es, die Sprache aus sich sebst heraus zu erweitern.

Implementiere mal, auch damit wir ein bisschen von der angeblichen "Fixierung" auf Metaprogrammierung wegkommen, die OOP-Aufgabe, welche ich ein paar Posts weiter oben gestellt habe.

Die Chancen stehen gut, dass du bewusst, oder unbewusst, das Besucher-Entwurfsmuster verwenden wirst. Diese tollen Muster (wenn nicht alle, dann wenigstens dieses), die alle zu kennen sich rühmen, sind aber vollkommen unnötig in CLOS. Wären sie es nicht, würde man sie unnötig machen. Warum? Weil es Schwachsinn ist, mit solchen Krücken zu arbeiten, wenn andere Sprachen demonstrieren, wie man mit so etwas umgeht: Man löst das Probem an der Wurzel, auf Sprachebene. Genauso wie ein schechtes Condition-System (Exceptions) nicht toleriert werden müsste.

Also komme ich, unabsichtich, schon wieder zur Metaprogrammierung. Alle Extrawürste der Welt und alle Meine-Sprache-Kanns-Nicht-Aber-Ich-Geb-Meiner-Notlösung-Nen-Namen-Entwurfsmuster können durch ein Konzept ersetzt werden:

Code ≡ Daten

Erstaunlich, was solch ein vermeintlich "unwichtiges" Konzept ermöglicht, nicht wahr?
T.M., auf Code zusammengekürzt schriebObiger Code ist schlicht nicht richtig. Der Code, der tatsächlich läuft, sieht so aus:
{
   ifstream i("Dateiname", ios::in);
   string line;
   while (i.good() && getline(i, line))
   {
      cout << line << endl;
   }

   // ...

} // <-- [1]

Im Fall, dass man exceptions behandelt, ist es ähnlich:

try
{
   AnyObject obj("Hugo");
   throw Exception();

   // ...

} // <-- [2]
catch (const Exception& x)
{
   // ...
}
</code>
Aussehen sollte er so:
with_open_file(i, "dateiname") {
   while (getline(i, line)) {
      cout << line << endl; 
   }
}
Wobei, wohl gemerkt, jeglicher Code innerhalb dieses Konstruktes stehen können soll.
Im Fall, dass beispielsweise die Datei nicht existiert oder nicht geöffnet werden kann, wird der Aufruf von i.good() das stream-Objekt in einen Fehlerzustand versetzen, woraufhin das while fehlschlägt.
Ach Du heilige Scheiße. Dazu sag ich besser nichts.

Ein kleines Verständnisproblem habe ich: Woher weiß der Stream, wann er geschossen werden soll? (Keine rhetorische Frage, sondern eine ernst gemeinte.)