Der Initialisierungs-Parameter birgt einige Fallstricke, erlaubt aber auch mehr Kontrolle.

Kleine Fallstricke

Wollen wir also mal sehen, was wir mit unserem sum3 so alles anstellen können ...

Impliziter Typ eines Literals für init

int   a[] = { 0,   1,   2,   3 };
float b[] = { 0.5, 1.5, 2.5, 3.5 };

int   sa = sum3(a, a+4, 0); // Yeah!
float sb = sum3(b, b+4, 0); // Ouch!

Das Ergebnis sb ist nicht ganz das erwartete ... was ist passiert? Eigentlich das gleiche wie im Beispiel mit den strings: Der Compiler bestimmt den Typ des Literals 0 nicht als float (warum auch?), sondern als int. Und wenn man Pech hat, sieht man auch keine Warnung, nirgends ... (der gcc warnt nicht mit -Wall, man muss -Wconversion bemühen). Also müssen wir wieder explizit werden:

float b[] = { 0.5, 1.5, 2.5, 3.5 };

float sb  = sum3(b, b+4, float(0)); // Like this!
float sb1 = sum3(b, b+4, 0.0);     //  Or like that?

Das ist aber unschön und eine ergiebige Fehlerquelle. (Ist übrigens 0.0 jetzt vom Typ float oder double? float(0) spricht dagegen eine eindeutige Sprache und ist verallgemeinerbar.) Diese Fallstricke wollen wir uns bzw. unseren Nutzern doch gerne ersparen — dazu gleich mehr. Doch zunächst …

Steuerungsmöglichkeiten durch init

Auf der anderen Seite ermöglicht die explizite Angabe des Ergebnis-Typs aber auch algorithmische Verbesserungen, indem ausdrücklich ein erweiterter Werte-Typ gewählt wird. Betrachten wir folgendes Beispiel:

vector<unsigned char> dice_rolls(BigN); 

// Ueberraschung
unsigned sum_dice_rolls = sum3(dice_rolls.begin(), dice_rolls.end(), (unsigned char)(0));

Hier erleben wir eine unliebsame Überraschung, wenn wir uns das Ergebnis anschauen. Der Typ unsigned char kann problemlos das Ergebnis eines einzigen Würfelwurfs enthalten, aber wenn N > 42 ist, dann kann die Summe schon zu groß sein: Der Wertetyp der Sequenz ist für die Summation zu eng! In diesem Fall wäre der Aufruf

unsigned sum_dice_rolls = sum3(dice_rolls.begin(), dice_rolls.end(), 0);

sogar (zufälligerweise) besser gewesen. Etwas expliziter könnten wir schreiben (wenn wir sicher sind, dass unsigned int groß genug ist):

unsigned sum_dice_rolls = sum3(dice_rolls.begin(), dice_rolls.end(), unsigned(0));

Selbst wenn das Endergebnis in den nominellen Wertetyp der Sequenz hineinpasst, kann es nötig sein, zur Berechnung einen erweiterten Typ heranzuziehen. Das ist insbesondere bei Fließkomma-Berechnungen der Fall. Generell sollte man hier Berechnungen immer mit doppelter Genauigkeit ausführen:

std::vector<float> a(1000000);
// ...
double a_sum = sum3(a.begin(), a.end(), double(0.0));

Design-Verbesserungen ...

Als Quintessenz unserer Überlegungen folgt, dass die explizite Auswahlmöglichkeit des Rechen-Wertetyp über den init-Parameter wichtige Steuerungsmöglichkeiten eröffnet, jedoch auch fehleranfällig ist. Also sollten wir überlegen, wie wir die Fehlerquellen ausschalten, aber die mit init verbundenen Möglichkeiten erhalten.

Die Fehler bei der Benutzung von sum3 kommen im wesentlichen aus 2 Richtungen:

  1. Die implizite Angabe des Typs von init durch ein Literal wie 0, was zu einem ungeeigneten Wertetyp führen kann
  2. Die Verwendung eines zu kleinen Wertetyps für die Berechnungen und das Ergebnis

Fehlerquelle 1 ist in der Tat ein Problem, das durch die generische Implementierung erzeugt wird. Fehler 2 kann zwar auch durch Fehler 1 ausgelöst werden, ist aber genauso bei expliziter Angabe des Typs von init möglich und kann (und wird!) auch in "handgestricktem" Code auftreten. Wir kommen später auf Fehler vom Typ 2 zurück.

Um die 1. Fehlerquelle zu beseitigen, können wir z.B. die explizite Angabe des Typs erzwingen. Dazu ändern wir die Schnittstelle so, dass dieser Typ als Argument mitübergeben werden muss:

template<class T>
struct value {
  typedef T type;
  type val;

  value(type const& t) : val(t) {}
  // make T t = value<T>(v) possible
  operator type() { return val; }
};

template<typename Iterator, typename T>
T
sum3b(Iterator  a, Iterator a_end, value<T> init)
{
  T res = init;
  for(; a != a_end; ++a)
    res += *a;
  return res;
}

Damit müssen wir sum3b jetzt so aufrufen:

float a[] = { 0.0, 0.5, 1.0, 1.5 };
float suma = sum3b(a, a+4, value<float>(0));

Das ist jetzt, zugegeben, etwas umständlicher. Wie sieht es denn mit den "normalen" Fällen aus, in denen der Wertetyp des Iterator und der Rechen-Wertetyp zusammenfallen können?