zurück zum Artikel

C++ Core Guidelines: Weitere Regeln zu Templates

Rainer Grimm

Da sich die verbleibenden Regeln zu Templates nicht unter einem gemeinsamen Begriff zusammenfassen lassen, sind die heterogenen Regeln in dem Abschnitt "other" der C++ Core Guidelines gelandet. Diese beschĂ€ftigen sich mit Best Practices und Überraschungen.

Da sich die verbleibenden Regeln zu Templates nicht unter einem gemeinsamen Begriff zusammenfassen lassen, sind die heterogenen Regeln in dem Abschnitt "other" der C++ Core Guidelines gelandet. Diese beschĂ€ftigen sich mit Best Practices und Überraschungen.

Hier ist meine Plan fĂŒr heute.

Bei der ersten Regel geht es gleich um BewÀhrtes.

Ehrlich gesagt, weiß ich nicht, warum diese Regeln zu den Regeln fĂŒr Templates gehört. Vielleicht, da es bei Templates um Wiederverwendung geht oder da das Beispiel den std::find_if-Algorithmus der Standard Template Library verwendet. Egal! Diese Regel ist entscheidend fĂŒr guten Code.

Stelle dir vor, du besitzt einen Vektor von einfachen DatensĂ€tzen. Jeder Datensatz besteht aus einem Namen, einer Adresse und einer ID. Oft stehst du vor dem Problem, einen bestimmten Datensatz mit einem spezifischen Namen zu finden. Die Aufgabe ist noch schwieriger, denn du achtest bei dem Name nicht auf dessen Groß- oder Kleinschreibung:

// records.cpp

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>
#include <vector>

struct Rec { // (1)
std::string name;
std::string addr;
int id;
};

int main(){

std::cout << std::endl;

std::vector<Rec> vr{ {"Grimm", "Munich", 1}, // (2)
{"huber", "Stuttgart", 2},
{"Smith", "Rottenburg", 3},
{"black", "Hanover", 4} };

std::string name = "smith";

auto rec = std::find_if(vr.begin(), vr.end(), [&](Rec& r) { // (3)
if (r.name.size() != name.size()) return false;
for (int i = 0; i < r.name.size(); ++i){
if (std::tolower(r.name[i]) != std::tolower(name[i])) return false;
}
return true;
});

if (rec != vr.end()){
std::cout << rec->name << ", " << rec->addr << ", " << rec->id << std::endl;
}

std::cout << std::endl;

}

Die Struktur rec (Zeile 1) besitzt nur öffentliche Mitglieder. Daher lĂ€sst sie sich direkt mit Aggregat-Initialisierung [5]in Zeile 2 initialisieren. In der Zeile suche ich mithilfe der Lambda-Funktion nach dem Datensatz, der den Namen "smith" besitzt. Daher prĂŒfe ich zuerst, ob beide Namen gleich lang sind und dann vergleiche ich die Buchstaben unabhĂ€ngig von ihrer Groß- oder Kleinschreibung.

C++ Core Guidelines: Weitere Regeln zu Templates

Welches Problem besitzt der Sourcecode? Die Anforderung, Strings unabhĂ€ngig von dessen Groß- oder Kleinschreibung zu vergleichen, tritt sehr hĂ€ufig auf. Daher sollte die Lösung der Anforderung in eine Einheit verpackt werden und einen Namen erhalten:

bool compare_insensitive(const std::string& a, const std::string& b) // (1)
{
if (a.size() != b.size()) return false;
for (int i = 0; i < a.size(); ++i){
if (std::tolower(a[i]) != std::tolower(b[i])) return false;
}
return true;
}

std::string name = "smith";

auto res = std::find_if(vr.begin(), vr.end(),
[&](Rec& r) { compare_insensitive(r.name, name); }
);

std::vector<std::string> vs{"Grimm", "huber", "Smith", "black"}; // (2)

auto res2 = std::find_if(vs.begin(), vs.end(),
[&](std::string& r) { compare_insensitive(r, name); }
);

Die Funktion compare_insensitive (Zeile 1) gibt der typischen Anforderung einen Namen. Nun lÀsst sie sich auf einen Vektor von Strings in Zeile 2 anwenden.

Zugegeben, ich habe hÀufig eine Diskussion in meinen Schulungen zu der Frage: Wann soll eine Funktion (Funktionsobjekt) oder eine Lambda-Funktion eingesetzt werden? Ehrlich gesagt, ich habe keine einfache Antwort auf diese Frage. Hier widersprechen sich zwei wichtigen Regeln zur CodequalitÀt:

  1. Don't repeat yourself. (DRY [7])
  2. Explicit is better than implicit. (The Zen of Python [8])

Die zweite Regel musste ich mir von Python ausleihen. FĂŒr was steht diese Regel. Stelle dir vor, du hast einen altbackenen Fortran-Programmierer in deinem Team. Dieser entgegnet dir: "Jeder Bezeichner muss genau drei Buchstaben lang sein." Daher kann dein Sourcecode zeitweise das folgende Aussehen besitzen:

auto eUE = std::remove_if(use.begin(), use.end(), igh); 

FĂŒr was steht zum Beispiel die Funktion igh? Sie steht fĂŒr eine "id greater hundred". Nun bist du gezwungen, diesen Funktionsaufruf zu dokumentieren.

Wenn du hingegen eine Lambda-Funktion verwendest, dokumentiert sich der Code selbst:

auto earlyUsersEnd = std::remove_if(users.begin(), users.end(),
[](const User &user) { return user.id > 100; });

Glaube mir, ich hatte Diskussionen mit Fortran-Programmierern zu Bezeichnern. Zugegeben, mehr Argumente wie CodelokalitĂ€t oder CodegrĂ¶ĂŸe sprechen fĂŒr oder gegen Lambda-Funktionen. Die zwei Argumente "Don't repeat yourself" versus "Explicit is better than implicit" sind fĂŒr mich die zwei entscheidenden Argumente.

Ein einfaches Beispiel sagt mehr als eine lange ErlÀuterung. In dem folgenden Beispiel iteriere ich durch einen std::vector, eine std::deque und eine std::list:

// notGeneric.cpp

#include <deque>
#include <list>
#include <vector>

template <typename Cont>
void justIterate(const Cont& cont){
const auto itEnd = cont.end();
for (auto it = cont.begin(); it < itEnd; ++it) { // (1)
// do something
}
}

int main(){

std::vector<int> vecInt{1, 2, 3, 4, 5};
justIterate(vecInt); // (2)

std::deque<int> deqInt{1, 2, 3, 4, 5};
justIterate(deqInt); // (3)

std::list<int> listInt{1, 2, 3, 4, 5};
justIterate(listInt); // (4)

}

Der Code wirkt unschuldig. Wenn ich aber das Programm ĂŒbersetze, bricht dieser Prozess mit einer Fehlermeldung von circa 100 Zeilen ab:

C++ Core Guidelines: Weitere Regeln zu Templates

Der Anfang der Fehlermeldung ist sehr prĂ€zise: "notGeneric.cpp:10:37: error: no match for ‘operator<’ (operand types are ‘std::_List_const_iterator".

Was ist das Problem? Das Problem ist in der Zeile 1. Der Iteratorvergleich (<) ist fĂŒr den std::vector (Zeile 2) und fĂŒr die std::deque (Zeile 3) okay, bricht aber fĂŒr die std::list (Zeile 4). Jeder Container gibt einen Iterator zurĂŒck, der seine Struktur reprĂ€sentiert. Das ist im Falle des std::vector und der std::deque ein Random Access Iterator und im Falle der std::list ein Bidirectional Iterator. Ein Blick auf die Iteratorkategorien schafft AufklĂ€rung:

C++ Core Guidelines: Weitere Regeln zu Templates

Die Random-Access-Iterator-Kategorie ist eine Obermenge der Bidirectional-Iterator-Kategorie und Letztere ist eine Obermenge der Forward-Iterator-Kategorie. Nun ist das Problem offensichtlich. Ein Iterator, der von einer Liste erzeugt wird, unterstĂŒtzt den Kleiner-Vergleich nicht. Die Lösung des Problems ist einfach. Iteratoren aller Kategorien unterstĂŒtzen den !=-Vergleich. Hier ist das generische justIterate-Funktions-Template, das mit den Container der Standard Template Library kann.

template <typename Cont>
void justIterate(const Cont& cont){
const auto itEnd = cont.end();
for (auto it = cont.begin(); it != itEnd; ++it) { // (1)
// do something
}
}

Eine Bemerkung kann ich mir nicht verkneifen. Es ist meist eine schlechte Idee, hÀndisch durch einen Container wie in der Funktion justIterate zu iterieren. Dies ist die Aufgabe eines passenden Algorithmus der Standard Template Library.

Mein ursprĂŒnglicher Plan war es, in diesem Artikel auf die Regel "T.144: Don’t specialize function templates [10]" einzugehen. Die Regel besitzt ein großes Überraschungspotenzial. Mein nĂ€chster Artikel zeigt, warum. ( [11])


URL dieses Artikels:
https://www.heise.de/-4303052

Links in diesem Artikel:
[1] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-name
[2] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-lambda
[3] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-nongeneric
[4] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-name
[5] https://en.cppreference.com/w/cpp/language/aggregate_initialization
[6] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-lambda
[7] https://en.wikipedia.org/wiki/Don't_repeat_yourself
[8] https://www.python.org/dev/peps/pep-0020/
[9] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-nongeneric
[10] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-specialize-function
[11] mailto:rainer@grimm-jaud.de