SFINAE-basierte Techniken

Content

Bei SFINAE (substitution failure is not an error) schließen wir Kandidaten aus dem Kandidatenpool für bestimmte Template-Parameter aus, wenn gewisse Template-Abhängigkeiten nicht erfüllt sind.

Wie wir zuvor bereits gesehen haben, ist decltype hier ein sehr hilfreiches Werkzeug, da es nicht nur eingesetzt werden kann, um den Datentyp eines Ausdrucks zu bestimmen und an geeigneter Stelle einzufügen, sondern auch dazu genutzt werden kann, das Vorhandensein all der gewünschten Operatoren, Methoden und Funktionen sicherzustellen. Hier kommt diese Technik noch einmal zum Einsatz an einem etwas komplexeren Beispiel:

#ifndef SUM_HPP
#define SUM_HPP

template<typename IT>
auto sum(IT begin_it, IT end_it) -> decltype(*begin_it + *begin_it++) {
   using T = decltype(*begin_it + *begin_it++);
   T sum{};
   while (begin_it != end_it) {
      sum += *begin_it++;
   }
   return sum;
}

#endif
#include <iostream>
#include "dim.hpp"
#include "sum.hpp"

int main() {
   int values[] = {2, 3, 5, 7, 11, 13};
   std::cout << sum(values, values + dim(values)) << std::endl;
}
theon$ g++ -Wall -o sum sum.cpp
theon$ ./sum
41
theon$ 

Warum wurde hier bei decltype der Ausdruck *begin_it + *begin_it++ verwendet? Hätte es nicht auch *begin_it getan?

Wir gewinnen damit folgende Vorteile:

Aufgabe

Entwickeln Sie eine Template-Funktion für das Skalarprodukt für zwei beliebige Container, die

Welches der vier Kriterien ist zur Übersetzzeit nicht immer überprüfbar? Wie könnte das zur Laufzeit überprüft werden?

Denken Sie daran, dass Sie bei decltype bequem mehrere Ausdrücke mit Hilfe des Komma-Operators verketten können. Der letzte Ausdruck bestimmt dann den Typ des decltype-Konstrukts.

Achten Sie darauf, dass sowohl Container wie std::vector als auch traditionelle Arrays unterstützt werden, d.h. Sie können sich nicht darauf verlassen, dass Methoden begin und end existieren. Eine Abhilfe bieten hier die Funktionen std::begin und std::end aus #include <iterator>, die sowohl für Objekte mit den entsprechenden Funktionen als auch Zeiger funktionieren.

Sie können sich überlegen, wie Sie durch die beiden Container durchiterieren. Wenn Sie möchten, können Sie darauf bestehen, dass Sie einen random access iterator haben. Prinzipiell ist es aber auch möglich, ohne dim und ohne einen random access iterator auszukommen.

Folgendes Testprogramm sollte dann funktionieren:

#include <iostream>
#include <vector>
#include "dotprod.hpp"

int main() {
   std::vector<double> a = {2.0, 4.5, 7.0};
   int b[] = {4, 5, 6};
   std::cout << "a dot b = " << dotprod(a, b) << std::endl;
}