Die Summe muss initialisiert werden, dafür gibt es mehrere Optionen.

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:

  1. Wir verwenden eine Technik analog zu value_type
  2. Wir übergeben das neutrale Element als zusätzliches Argument init
  3. 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 ...