Ein neutrales Element? Wird's jetzt mathematisch?
Was passiert jetzt, wenn wir unser
sum2
für einen Vektor von strings aufrufen?
Die entscheidende Stelle ist die Initialisierung von res
:
template<typename Iterator>
typename value_type<Iterator>::result
sum2(Iterator a, Iterator a_end)
{
typename value_type<Iterator>::result res = 0;
for(; a != a_end; ++a)
res += *a;
return res;
}
Was sollte hier eigentlich passieren, wenn wir eine Sequenz von std::string
übergeben?
res
sollte mit dem leeren String initialisiert werden.
Und ganz allgemein sollte res
mit dem Wert initialisiert werden,
der dem neutralen Element bzgl. der Addition entspricht.
Bei den eingebauten Zahlentypen ist das die 0, deswegen funktioniert sum2
dafür auch.
Woher bekommen wir hier jetzt den richtigen Wert zu Initialisierung?
Alternativen für die Initialisierung der Summe
Dafür gibt es mehrere Möglichkeiten:
- Wir verwenden eine Technik analog zu
value_type
- Wir übergeben das neutrale Element als zusätzliches Argument
init
- Wir initialisieren
res
mit dem ersten Element der Seqeuenz und beginnen die Iteration mit dem 2. Element
Alle diese Lösungen haben Vor- und Nachteile. Die erste Lösung bedeutet einen gewissen Mehraufwand von Seiten des Nutzers, wenn "sein" Typ noch nicht unterstützt wird. Ausserdem kann es Typen geben, in denen der korrekte Wert des neutralen Elements erst zur Laufzeit bestimmt werden kann, z.B. ein Matrixtyp mit zur Laufzeit bestimmten Dimensionen. Auf diese Lösung kommen wir später zurück und erweitern sie dann in einem allgemeineren Kontext.
Die 2. Lösung macht das Interface komplizierter, erlaubt allerdings das Verschachteln von Aufrufen:
x = sum(a.begin(), a.end(), sum(b.begin(), b.end(), init));
Zudem wird das Problem u.U. nur auf eine höhere Ebene verschoben, wenn die aufrufende Funktion selbst generisch ist und das neutrale Element daher auch nicht kennt. Andererseits wird Implementierung dadurch etwas allgemeiner, denn der übergebene Initialisierungs-Wert könnte durchaus einen anderen Typ als der Werte-Typ von Iterator haben (das ist allerdings auch eine Gefahr ... das diskutieren wir weiter unten.).
Die 3. Lösung behält das einfache Interface, initialisiert automatisch korrekt, und ist möglicherweise etwas effizienter. Allerdings funktioniert die Lösung nicht mehr für leere Sequenzen. Das sieht zwar nicht nach einer großen Einschränkung aus, aber wenn die Funktion innerhalb einer größeren Schleife eingesetzt wird, in der Teilsummen berechnet werden, kann dies u.U. eine Menge Tests ersparen.
Ein Lösungsansatz für die Initialisierung
Versuchen wir also einmal, die Stärken der unterschiedlichen Lösungen zu kombinieren. Lösung 2 ist sicher am allgemeinsten, wenn auch u.U. für den Nutzer unbequemer. Daher bietet es sich an, diese Lösung als Basis zu implementieren, und die anderen später als "convenience wrapper" zur Verfügung zu stellen. Wir könnten dabei noch zur Compile-Zeit testen, ob es ein eindeutiges neutrales Element gibt, um dann auf Lösung 1 und andernfalls Lösung 3 zu verzweigen. Damit können nur dann keine leeren Sequenzen mit dem vereinfachten Interface aufsummiert werden, wenn es mangels eindeutigem neutralen Element ohnehin keinen Sinn macht.
Widmen wir uns damit zunächst Lösung 2 mit dem zusätzlichen init-Argument:
template<typename Iterator, typename T>
T
sum3(Iterator a, Iterator a_end, T init)
{
T res = init;
for(; a != a_end; ++a)
res += *a;
return res;
}
Nächster Versuch mit strings ...
Jetzt steht einem Aufruf mit einem Vektor von strings nichts mehr im Wege:
std::string words[] = { "This", "is", "a", "sentence" };
// ...
std::string sentence = sum3(words, words+4, ""); // Autsch.
Leider spielt der Compiler nicht mit,
denn er fasst das Literal ""
nicht als std::string
auf, sondern als const char*
.
Daher müssen wir etwas expliziter sein:
std::string sentence = sum3(words, words+4, std::string("")); // So!
Jetzt funktionierts!
(Psst: Ist eigentlich der Output in sentence
so,
wie Sie ihn gerne hätten?
Und sollte man das
für strings überhaupt so machen?)
Probieren wir also mal aus,
was sich mit diesem zusätzlichen Parameter init
so alles tun lässt ...