Antrag für einen Vortrag auf der FrOSCon 24
===========================================
:author: Ichthyostega
:date: 23.5.2024


Alt werden mit C++
------------------

Kurzfassung
~~~~~~~~~~~

Für eine langlebige Codebasis ist der Charakter und die weitere Entwicklung der einmal gewählten
Programmiersprache bedeutsam. Dies wird verdeutlicht anhand von Beispielen und Erfahrungen im Projekt
Lumiera mit der Sprache C++. Diese Sprache bietet Abstraktionsmittel, die sehr hilfreich sein können,
in einer großen und komplexen Codebasis die Flexibilität und Änderbarkeit zu erhalten. Allerdings tritt
die schrittweise Evolution dieser Sprachmittel in Wechselwirkung mit der Entwicklung der Codebasis und
erzeugt Wartungsprobleme. Dies wird beleuchtet am Beispiel von Lambdas und Funktoren, der Einführung
von RValue-Referenzen, sowie der Entwicklung von Generics und Concepts.


Langfassung
~~~~~~~~~~~

Software lebt solange sie änderbar ist.
Für langlebige Software ist der Charakter der Programmiersprache bedeutsam. Der Charakter jeder Sprache,
und die damit verbundenen Methoden entwickeln sich weiter. Daraus entsteht eine Wechselwirkung mit einer
bestehenden Codebasis, die sich ebenfalls weiterentwickelt.

Exemplarisch sei dies am Beispiel der Erfahrungen im Projekt Lumiera mit der Sprache C++ nachgezeichnet.
In diesem OpenSource-Projekt wird Software für professionellen Film/Videoschnitt entwickelt.

Entscheidung für eine Plattform ist im Kern eine Entscheidung für ein Ökosystem. Ein langlebiges Projekt
muß auf Autonomie und Handlungsfähigkeit achten. Daher scheiden die meisten zunächst offensichtlichen und
beliebten Plattformen aus. Für Lumiera hat man sich damals für C++ in einem Unix-Umfeld entschieden.
Realistische andere Optionen wären noch gewesen: D, Java, plain-C, Haskell.
Einmal getroffen, ist eine solche Entscheidung mehr oder weniger endgültig.
Denn jede der großen Programmiersprachen steht auch für eine bestimmte
Denkhaltung und Herangehensweise.

Software bleibt nur änderbar, wenn sie mit den beschränkten menschlichen Fähigkeiten
im Einklang steht. Das gilt, sofern Software von Menschen und für Menschen gebaut wird.
Die wichtigste Beschränkung liegt dabei in der Fähigkeit, die Folgen von Änderungen
zu verstehen und zu beherrschen. Das wird ermöglicht durch den sachgemäßen Ansatz
von Abstraktionen.

Durch einen Interface-Kontrakt wird die Implementierung zum Detail, und damit lokal
änderbar. Für einen Kontrakt muß aber auch das Verhalten festgelegt werden. Damit dringt
die Abstraktion in die Implementierung ein, und erfordert entsprechende Ausdrucksmittel.
Der vorherrschende Lösungsansatz hierfür war in den letzten 20 Jahren die Trennung von Infrastruktur
und Funktionalität. Ein Stück Funktionalität wird zu einem abstrakten Element, das in einen
anderweitig geregelten Kontrakt eingebunden werden kann. Einmal etabliert, könnte ein solches
Schema oft an weiteren Stellen angewendet werden, sofern man auch noch von den konkreten
Funktionsargumenten abstrahieren könnte. So entsteht die Forderung nach Funktionen
auf generischen Argumenten.

In der Praxis war der Weg dorthin mühsam und erforderte viele Workarounds. Die Sprachmittel in C++
waren dafür anfangs wenig geeignet und problematisch: im Grunde gab es nur die schon in C nutzbaren
Funktions-Pointer. Jedoch hat C++ durch seine Klassen und das Typsystem eine ausgeprägte Stärke
im Bauen neuer Abstraktionen. Die Boost-Libraries haben daraus ein neues Sprachmittel geschaffen:
Funktions-Objekte. Mit C++11 wurden diese auch Teil der Sprachdefinition; zugleich jedoch wurden
auch Lambda-Ausdrücke als neues Sprachmittel eingeführt. Beide Sprachmittel stehen nun gleichberechtigt
nebeneinander und ermöglichen völlig neue Anwendungsmuster. Und die Sprache entwickelte sich weiter.
Mithilfe der RValue-References wurde das transparente Durchreichen von Argumenten möglich. Die hierfür
notwendigen semantischen Anpassungen sind subtil und tückisch. Eine schrittweise Umstellung einer großen
Codebasis ist ein langlaufender Vorgang und eine organisatorische Herausforderung.

Oft muß man aber Argumente nicht nur durchreichen, sondern eine generische Funktion muß
Eigenschaften ihrer Argumente verstehen und sich daran anpassen können. In den meisten älteren
Programmiersprachen scheitert an dieser Forderung die gesamte Weiterentwicklung in Richtung einer
Funktions-Abstraktion. Zum Glück (oder leider) galt das nicht für C++, denn während der Standardisierung
der Sprache sind einige Design-Unfälle passiert, deren anfangs gar nicht intendiertes Verhalten in
den Compilern dazu verwendet werden konnte, diese »Schallmauer« zu durchbrechen. Gemeint ist die
Template-Metaprogamierung, eine Art »schwarze Magie«, mit der man eine funktionale Hilfs-Sprache
während dem Compilier-Prozess ausführen lassen kann.

Warum läßt man sich auf schwarze Magie ein? Antwort: weil man dadurch mächtiger wird.
Die Sprachmittel werden ausdrucksstärker. Mit wenigen Zeilen Code kann man eine komplexe
Funktionalität zuverlässig und sicher realisieren. Dadurch gewinnt man Zeit, über die eigentlichen
Anforderungen nachzudenken. Man geht damit aber technische Schulden ein: der Umgang mit der
Codebasis erfordert vertiefte Kenntnisse, und einen mathematisch-geprägten Denkstil.

Trotzdem war diese Entwicklung in der Praxis derart erfolgreich, daß das C++ - Komittee ihr
gefolgt ist, und die Aufgabe übernommen hat, die historisch gewachsene schwarze Magie in
klar definierte und sauber verwendbare Sprachmittel inkrementell weiter zu entwickeln.

- Varidadische Ausdrücke
- Concepts

Wenn man diese Aufgabe ernst nimmt, kann man sich nicht damit begnügen, einen pragmatischen Gebrauch
zu codifizieren und syntaktisch nett zu verpacken. Vielmehr sind neue Sprachmittel gefordert, die
sich zwar in den Stil der Sprache nahtlos einfügen, und auch das gleiche leisten wie die historisch
gewachsene Lösung, aber dennoch auf einer neuen Basis stehen, klar definiert und leichter korrekt
verwendbar sind. Das ist eine sehr anspruchsvolle Aufgabe; das Ergebnis ist erstaunlich, hat aber
auch sehr viel Zeit in Anspruch genommen und ist in manchen Teilaspekten immer noch unfertig.

Diese schrittweise Weiterentwicklung der Sprache führt dazu, daß im bestehenden Code eine Menge von Hilfsmitteln
entsteht, die aufeinander aufbauen und weithin im Einsatz sind. Jedoch sind die Lösungen aus verschiedenen
Entwicklungsstadien untereinander oft nicht kompatibel, da sich die gesamte Herangehensweise geändert hat.
In einer größeren Code-Basis entstehen so komplexe interne Binding-Schichten, die erhebliche Probleme
bei der Wartung aufwerfen können: zusammenhängende Systeme ziehen sich über weite Strecken durch den Code,
und stützen sich jeweils auf Hilfsmittel ab, die untereinander nicht austauschbar sind.

Beispiele hierfür sind:
- Loki-Typlisten vs. Variadic Arguments
- Type-Traits vs. Concepts

Da die jeweiligen Alternativen den gleichen Zweck auf unterschiedlichem Weg erreichen, kann eine
Umstellung herausfordernd sein und läuft auf eine großflächige Überarbeitung etablierter Infrastrukturen
hinaus. Daraus erwächst aber auch die Chance, in der Codebasis weithin etablierte Lösungen
zu hinterfragen und weiterzuentwickeln.

