Überladungen
Content |
C++ unterstützt das Überladen (overloading) von Namen. Das betrifft insbesondere Deklarationen von Funktionen, Methoden und Templates. Überladungen sind nur zulässig, wenn eine Unterscheidung aufgrund der Parameter bzw. Template-Parameter möglich ist.
Folgende Überladung ist beispielsweise unzulässig, weil sich die beiden Funktionsdeklarationen nur beim Return-Typ unterscheiden und somit eine Differenzierung aufgrund der Parameter nicht möglich ist:
int mid(int a, int b) { return (a + b) / 2; } double mid(int a, int b) { return (a + b) / 2.0; }
theon$ g++ -Wall -c bad-overloading.cpp bad-overloading.cpp: In function 'double mid(int, int)': bad-overloading.cpp:5:8: error: ambiguating new declaration of 'double mid(int, int)' double mid(int a, int b) { ^~~ bad-overloading.cpp:1:5: note: old declaration 'int mid(int, int)' int mid(int a, int b) { ^~~ theon$
Solange die Unterscheidbarkeit gegeben ist, dürfen beliebig viele Deklarationen den gleichen Namen verwenden.
Wenn dann der Name genutzt wird, findet eine Auswahl in zwei Phasen statt:
-
Feststellung aller zulässigen Kandidaten. Kandidaten werden entfernt u.a. aufgrund
-
einer nicht passenden Parameterzahl (wobei hier Dinge wie Default-Werte und variabel lange Parameterlisten zu berücksichtigen sind),
-
einer fehlenden Konvertierbarkeit von den aktuellen zu den formalen Parametern und aufgrund
-
von SFINAE bei Templates.
-
-
Sortierung der verbliebenen Kandidaten nach einer Liste von Kriterien. Hierzu gehören in dieser Reihenfolge u.a. folgende Kriterien:
-
Konsequent größere Nähe bei der Konvertierbarkeit
-
Bessere Passung bezüglich der Referenzen
-
Nicht-Template-Funktionen oder -Methoden haben Vorrang vor Template-Funktionen und -Methoden.
-
Bessere Passung bei den Template-Parametern.
-
Wenn bei der ersten Phase kein Kandidat übrigbleibt, ist es ein Fehler. Neuere Übersetzer liefern dann typischerweise eine Liste der namentlich passenden Deklarationen, wobei sie dann jeweils begründen, warum sie nicht passen. Das führt oft zu längeren Fehlermeldungen:
#include <cstdlib> int foo(int) { return 0; } template<typename T> int foo(const T*) { return 1; } template<typename T, std::size_t N> int foo(const T (&a)[N]) { return N; } int main() { int i = foo(main); }
theon$ g++ -Wall -c no-candidates.cpp no-candidates.cpp: In function 'int main()': no-candidates.cpp:18:20: error: no matching function for call to 'foo(int (&)())' int i = foo(main); ^ no-candidates.cpp:3:5: note: candidate: int foo(int)int foo(int) { ^~~ no-candidates.cpp:3:5: note: conversion of argument 1 would be ill-formed: no-candidates.cpp:18:20: error: invalid conversion from 'int (*)()' to 'int' [-fpermissive] int i = foo(main); ^ no-candidates.cpp:8:5: note: candidate: template int foo(const T*) int foo(const T*) { ^~~ no-candidates.cpp:8:5: note: template argument deduction/substitution failed: no-candidates.cpp:18:20: note: types 'const T' and 'int()' have incompatible cv-qualifiers int i = foo(main); ^ no-candidates.cpp:13:5: note: candidate: template int foo(const T (&)[N]) int foo(const T (&a)[N]) { ^~~ no-candidates.cpp:13:5: note: template argument deduction/substitution failed: no-candidates.cpp:18:20: note: mismatched types 'const T [N]' and 'int()' int i = foo(main); ^ no-candidates.cpp:18:8: warning: unused variable 'i' [-Wunused-variable] int i = foo(main); ^ theon$
Die bei der zweiten Phase zur Anwendung kommende Ordnung ist nur partiell, d.h. diese Phase kann damit enden, dass wir am Ende mehrere gleichrangig gute Kandidaten haben. Das wird als Fehler behandelt:
#include <cstdlib> int foo(int) { return 0; } template<typename T> int foo(const T*) { return 1; } template<typename T, std::size_t N> int foo(const T (&a)[N]) { return N; } int main() { int i = foo("hi"); }
theon$ g++ -Wall -c ambiguous.cpp ambiguous.cpp: In function 'int main()': ambiguous.cpp:18:20: error: call of overloaded 'foo(const char [3])' is ambiguous int i = foo("hi"); ^ ambiguous.cpp:8:5: note: candidate: int foo(const T*) [with T = char] int foo(const T*) { ^~~ ambiguous.cpp:13:5: note: candidate: int foo(const T (&)[N]) [with T = char; long unsigned int N = 3] int foo(const T (&a)[N]) { ^~~ ambiguous.cpp:18:8: warning: unused variable 'i' [-Wunused-variable] int i = foo("hi"); ^ theon$
Die hier verwendete Zeichenkette "hi" ist vom Datentyp const char [3] (zwei Zeichen plus Nullbyte), die wegen der Dualität von Zeigern und Arrays aber auch ohne Konvertierung als const char* interpretiert werden kann.
Aufgabe
Schreiben Sie eine Funktion dim, die die Dimensionierung verschiedener Argumente liefert. Zu unterstützen sind
-
zur Übersetzzeit festdimensionierte Arrays,
-
Objekte, die eine Methode size() haben und
-
char**, wobei hier (wie bei argv) davon ausgegangen werden soll, dass ein Nullzeiger als Terminierung dient.
Folgendes Testprogramm sollte damit funktionieren:
#include <iostream> #include <vector> #include "dim.hpp" int main(int argc, char** argv) { std::cout << "argv: " << dim(argv) << std::endl; std::vector<int> a = {1, 2, 3, 4}; std::cout << "a: " << dim(a) << std::endl; int b[] = {5, 6, 7}; std::cout << "b: " << dim(b) << std::endl; }
theon$ g++ -Wall -o testit testit.cpp theon$ ./testit 1 2 3 4 5 6 argv: 7 a: 4 b: 3 theon$
Zu beachten sind folgende Punkte:
-
Alle Varianten sollten funktionieren, auch wenn die Parameter nur lesenderweise zur Verfügung stehen. Trickreich ist das bei char**, denn mit const char** klappt es nicht. Warum?
-
Sofern ein Ergebnis zur Übersetzzeit ermittelbar ist, sollten Sie beim Return-Typ constexpr angeben.
Würde es bei folgendem Beispiel Übersetzungsfehler geben?
#include <vector> #include "dim.hpp" std::vector<int> a = {1, 2, 3, 4}; int b[] = {5, 6, 7}; std::vector<int> vector_like_a(dim(a)); int array_like_a[dim(a)]; int array_like_b[dim(b)];
Überlegen Sie sich das kurz, ohne das mit dem g++ zu übersetzen. Wo kann hier constexpr zum Zuge kommen?
-
Setzen Sie SFINAE ein, wenn sonst eine Template-Funktion zu allgemein wäre.
Hinweis: In diesem Fall ist dies noch recht einfach, die Nutzung von decltype genügt. Bedenken Sie, dass der Return-Typ auch nach der Parameterliste stehen kann:
template<typename T> auto dim(/* ... */) -> decltype(/* ... */) { /* ... */ }
Mit diesem Konstrukt können wir auf einem Schlag
-
den Return-Typ richtig bestimmen und
-
dafür sorgen, dass dieser Kandidat per SFINAE ausscheidet, wenn der in decltype angegebene Ausdruck für T inkorrekt ist.
-