een lange titel

De som van een reeks in C++

Dit artikel legt uit hoe je op verschillende manieren de sam van een reeks gehele getallen kan berekenen.

De nodige includes

Vermits we in C++ programmeren, zijn dit de benodigde includes.

#include <iostream>
#include <functional>
#include <vector>
#include <memory>

Pointers doorgeven

De eerste variant is volledig in een C stijl met pointers geschreven. Om de lijst te bepalen wordt met begin een pointer naar het eerste element doorgegeven en met end wordt een pointer doorgegeven die voorbij het laatste element wijst. Dat laatste is misschien een eigenaardigheid maar dit wordt door de compiler toegestaan.

Deze programmeerstijl komt van assembler.

int som_a(int *begin, int *end)
{
   int s = 0;

   for (int *p=begin; p!=end; p++)
   {
      s += *p;
   }
   return s;
}

Uiteraard is s een lokale variabele die uiteindelijk teruggegeven wordt. Zo wordt som_a() gestart.

int tab1[] = {1, 2, 3, 4, 5};

int s1 = som_a(tab1, &tab1[5]);
std::cout << "s1 " << s1 << "\n";

Als einde wordt &tab1[5] doorgegeven.

De bewerking als lambdafunctie

In het volgende fragment wordt de bewerking als een lambdafunctie doorgegeven. Dit wordt dan de 3de parameter fu. Als we een lambdafunctie in een variabele willen opslaan nemen we meestal auto als type.

int som_b(int *begin, int *end, auto fu)
{
   int s = 0;

   for (int *p=begin; p!=end; p++)
   {
      s = fu(*p, s);
   }
   return s;
}

Hieronder wordt de lambdafunctie meegegeven met de oproep van som_b(). De lambdafunctie krijgt twee gehele getallen als parameter en geeft een geheel getal terug.

int s2 = som_b(tab1, &tab1[5], [](int g, int acc)->int
   {
      return g + acc;
   }
);
std::cout << "s2 " << s2 << "\n";

Template maakt algemeen

Om niet altijd de som() opnieuw te moeten schrijven als je met een andere type dan int wil werken, is template de uitkomst. Met deze oplssing worden een aantal types algemaan gemaakt. In de volgende variant zijn dat de type T en U. T is het type van de wijzer en U is het type van de waarde die in de lambdafunctie wordt behandeld.

Met std::function<U(U,U)> wordt het type van de lambdafunctie vastgelegd. Wanneer in het voorbeeld een concrete lambdafunctie wordt doorgegeven aan fu, zal U vervangen worden door int.

template<typename T, typename U>
int som_c(T begin, T end, std::function<U(U,U)> fu)
{
   U s = 0;

   for (T p=begin; p!=end; p++)
   {
      s = fu(*p, s);
   }
   return s;
}

In de oproep van som_c() verandert er niet veel ten opzichte van som_b().

int s3 = som_c<int *, int>(tab1, &tab1[5], [](int g, int acc)->int
   {
      return g + acc;
   }
);
std::cout << "s3 " << s3 << "\n";

Je kan ook een variabele maken om een lambdafunctie bij te houden. Dit gebeurt in het volgende fragment. Bij f1 wordt std::function gebruikt in de declaratie. Bij f2 is dat auto.

std::function<int (int, int)> f1 = [](int g, int acc)->int
   {
      return g + acc;
   };
std::cout << "f1 " << typeid(f1).name() << "\n";

auto f2 = [](int g, int acc)->int
   {
      return g + acc;
   };
std::cout << "f2 " << typeid(f2).name() << "\n";

int s4a = som_c(&tab1[0], &tab1[5], f1);

// geeft fout
//int s4b = som_c(&tab1[0], &tab1[5], f2);

Het blijkt nu wel dat bij het doorgeven van f2 met auto als type niet compatibel is met de parameter met als type std::function<U(U,U)>. Daarom wordt nog een variant van som() gemaakt.

Een variant met template

Het type std::function<U(U,U)> wordt nu vervangen door Fu. Nu kunnen auto lambda’s wel doorgegeven worden.

template<typename T, typename Fu>
int som_d(T begin, T end, Fu fu)
{
   int s = 0;

   for (T p=begin; p!=end; p++)
   {
      s = fu(*p, s);
   }
   return s;
}

Nu kan f2 wel doorgegeven worden.

int s4c = som_d(&tab1[0], &tab1[5], f2);

In de lambdafunctie kan ook auto als parametertype gebruikt worden.

int s4 = som_d(&tab1[0], &tab1[5], [](auto g, auto acc)->decltype(g)
   {
      return g + acc;
   }
);
std::cout << "s4 " << s4 << "\n";

int s5 = som_d(&tab1[0], &tab1[5], [](int g, int acc)->decltype(g)
   {
      return g + acc;
   }
);
std::cout << "s5 " << s5 << "\n";

int s6 = som_d(&tab1[0], &tab1[5], [](int g, int acc)->int
   {
      return g + acc;
   }
);
std::cout << "s6 " << s6 << "\n";

Hierboven staan een aantal varianten van de lambdafunctie.

Het is ook mogelijk om in de oproep verwijzingen naar de elementen van een vector door te geven.

std::vector<int> tab2 { 1, 2, 3 ,4 ,5 };
int s7 = som_d(&tab2[0], &tab2[5], [](int g, int acc)->int
   {
      return g + acc;
   }
);
std::cout << "s7 " << s7 << "\n";

int s8 = som_d(tab2.begin(), tab2.end(), [](int g, int acc)->int
   {
      return g + acc;
   }
);
std::cout << "s8 " << s8 << "\n";

Bij vector kan je voor de verwijzingen naast de arraynotatie ook begin() en end() gebruiken.