Liebe Leserinnen, Liebe Leser,
heute habe ich in Bezug auf STL-Algorithmen über ein einfaches und simultan interessantes Problem gestolpert. In den meisten Fällen ist die Verwendung dieser Algorithmen ein Selbstgänger, aber als es sich herausstellte, die Auflösung der Funktionsüberladungen kann während der Verwednung von STD-Algorithmen ein bisschen problematisch sein. Unten gibt es ein paar Beispiele.
Der unten zu sehende Code-Ausschnitt zeigt einen gewöhnlihen Fall:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
std::string capitalizeElem(const std::string& elem)
{
if (!elem.empty()) {
std::string tempElem = elem;
tempElem[0] = std::toupper(tempElem[0]);
return tempElem;
}
return elem;
}
template<typename Cont>
void printContainer(const Cont& container) {
for (auto elem : container) {
std::cout << elem << std::endl;
}
}
int main()
{
std::vector<std::string> fruitVec{"apple", "plum", "pear", "banana", "orange", "grape"};
std::vector<std::string> capitalFruitVec;
std::transform(std::cbegin(fruitVec),
std::cend(fruitVec),
std::back_inserter(capitalFruitVec),
capitalizeElem
);
printContainer(capitalFruitVec);
}
Es gibt etwas Raffiniertes hier nicht: die capitalizeElem Funktion ersetzt die ersten Buchstaben von Obst Namen durch Großbuchstaben, und dann wird die Ausgabe von std::transform in einen neuen std::vector gesammelt. Aber was würde passieren, wenn eine Überladung von capitalizeElem vorhanden ist, weil Conainer, die QString Elemente enthalten können, unterstüzt werden sollen?
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
std::string capitalizeElem(const std::string& fruit)
{
if (!fruit.empty()) {
std::string tempFruit = fruit;
tempFruit[0] = std::toupper(tempFruit[0]);
return tempFruit;
}
return fruit;
}
std::string capitalizeElem(const QString& fruit) {
return capitalizeElem(fruit.toStdString());
}
Im Falle capitalizeElem aufgerufen wird, wird die Auflösung der Funktionüberladung nicht funktionieren, weil der Compiler keine Möglichkeit hat, den Typ von Containerelemente festzustellen. Der Compiler meldet die gleiche Sache:
Shell
error: no matching function for call to ‘transform(std::vector<std::__cxx11::basic_string<char> >::const_iterator, std::vector<std::__cxx11::basic_string<char> >::const_iterator, std::back_insert_iterator<std::vector<std::__cxx11::basic_string<char> > >, <unresolved overloaded function type>)’Glücklicherweise können derselben Probleme mithilfe der Lambdas gelöst werden, weil sie (einfach gesagt) vor dem Aufruf von capitalizeElem die dringend benötigte Typinformation hinzufügen, und somit funktioniert die Auflösung von Funktionsüberladung erneut:
C++
1
2
3
4
5
std::transform(std::cbegin(fruitVec),
std::cend(fruitVec),
std::back_inserter(capitalFruitVec),
[](const std::string& fruit){return capitalizeElem(fruit);}
);
Das ist natürlich ein naiver Ansatz, weil der Typ der Elemente des Containers auf diese Weise befestigt ist. Das ist nicht ideal, aber moderne C++ kann einem zu Hilfe eilen:
C++
1
2
3
4
5
std::transform(std::cbegin(fruitVec),
std::cend(fruitVec),
std::back_inserter(capitalFruitVec),
[](const auto& fruit){return capitalizeElem(fruit);}
);
Durch die Verwendung von auto anstelle von std::string im Lambda wird die Tür dem Compiler eröffnen, den Typ der Containerelemente festzustellen, Mit dieser Information kann dann der Compiler die richtige Funktionsüberladung aufrufen. Einfach gesagt: durch die Verwednung von Typbestimmung während Kompilierzeit ist es möglich, die Funktionsüberladung wieder einzuschalten. Hier ist der letzte Code:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
std::string capitalizeElem(const std::string& elem)
{
if (!elem.empty()) {
std::string tempElem = elem;
tempElem[0] = std::toupper(tempElem[0]);
return tempElem;
}
return elem;
}
std::string capitalizeElem(const QString& fruit) {
return capitalizeElem(fruit.toStdString());
}
template<typename Cont>
void printContainer(const Cont& container) {
for (auto elem : container) {
std::cout << elem << std::endl;
}
}
int main()
{
std::vector<std::string> fruitVec{"apple", "plum", "pear", "banana", "orange", "grape"};
QList<QString> berryList{"blackberry", "raspberry", "boysenberry", "strawberry"};
std::vector<std::string> capitalFruitVec;
QList<std::string> capitalBerryList;
std::transform(std::cbegin(fruitVec),
std:: cend(fruitVec),
std::back_inserter(capitalFruitVec),
[](const auto& fruit){return capitalizeElem(fruit);}
);
std::transform(std::cbegin(berryList),
std::cend(berryList),
std::back_inserter(capitalBerryList),
[](const auto& fruit){return capitalizeElem(fruit);}
);
printContainer(capitalFruitVec);
printContainer(capitalBerryList);
}
Also, die Lehre des Tags ist:
Um bei der Auflösung von Funktionsüberladung zu helfen, kann die Typbestimmung von Lambdas während Kompilierzeit verwendet werden.