Der folgende Code zeigt den Kern eines C++ Metaprogrammierung Muster, das ich verwendet haben, um zu bestimmen, ob ein Typ T
eine Instanz einer bestimmten Klasse Vorlage:Funktion Vorlage Überladungsauflösung mit einem Zeiger Argumente
#include <iostream>
template<class A, class B>
struct S{};
template<class A, class B>
constexpr bool isS(const S<A,B>*) {return true;}
template<class T>
constexpr bool isS(const T*) {return false;}
int main() {
S<int,char> s;
std::cout<<isS(&s)<<std::endl;
return 0;
}
Es verfügt über zwei Überladungen einer constexpr
Funktionsvorlage isS
, und es gibt wie erwartet 1
aus. Wenn ich um den Zeiger von der zweiten isS
entfernen, d.h. ersetzen sie durch
template<class T>
constexpr bool isS(const T) {return false;}
das Programm unerwartet 0
ausgibt. Wenn beide Versionen von isS
bis zur Überladungsauflösungsphase der Kompilierung durchgehen, impliziert die Ausgabe, dass der Compiler die zweite Überladung auswählt. Ich habe dies unter GCC, Clang und VC++ mit den Online-Compiler here getestet, und sie alle produzieren das gleiche Ergebnis. Warum passiert das?
Ich habe Herb Sutter "Why Not Specialize Function Templates" Artikel mehrmals gelesen, und es scheint, dass beide isS
Funktionen als Basisvorlagen betrachtet werden sollten. Wenn das so ist, dann ist es eine Frage, welche die am meisten spezialisierte ist. Geht man von Intuition und this answer, würde ich erwarten, dass die erste isS
die am meisten spezialisierte sein, weil T
jede Instantiierung von S<A,B>*
übereinstimmen kann, und es gibt viele mögliche Instanziierungen von T
, die S<A,B>*
nicht übereinstimmen können. Ich möchte den Absatz in dem Arbeitsentwurf suchen, der dieses Verhalten definiert, aber ich bin nicht ganz sicher, welche Phase der Kompilierung das Problem verursacht. Hat es etwas damit zu tun? "14.8.2.4 Ableiten von Vorlagenargumenten bei teilweiser Bestellung"?
Dieses Problem wird besonders überraschend, da der folgende Code, in dem die erste isS
einen Verweis auf const S<A,B>
nimmt und die zweite nimmt einen const T
, gibt den Erwartungswert 1
:
#include <iostream>
template<class A, class B>
struct S{};
template<class A, class B>
constexpr bool isS(const S<A,B>&) {return true;}
template<class T>
constexpr bool isS(const T) {return false;}
int main() {
S<int,char> s;
std::cout<<isS(s)<<std::endl;
return 0;
}
So scheint das Problem zu etwas damit zu tun haben, wie Zeiger behandelt werden.
Ein 'const T'-Parameter ist äquivalent zu einem 'T'-Parameter (Sie erhalten also eine genaue Übereinstimmung), und Sie müssen eine Qualifizierungskonvertierung von' S * 'nach' S const * 'durchführen. Verwenden Sie 'S const s;' statt in Ihrem 'main'. - Der * spezialisierter * Test passiert sehr spät in der Überladungsauflösung, wenn andere Tests sich nicht zwischen den Überladungen entscheiden konnten. Hier können wir den zweiten Punkt früher auswählen, weil er einen besseren Rang hat (exakte Übereinstimmung vs Qualifikationsanpassung). –
dyp
@dyp Ok, aber warum wählt der Compiler dann das erste "isS" im Code-Snippet am unteren Ende aus, mit Referenzen? Muss der Compiler in diesem Fall keine Qualifizierungskonvertierung von 'S &' nach 'const S &' vornehmen? –
Ose
@Ose Nein, 'T *' zu 'const T *' ist eine Qualifizierung Anpassungskonvertierung, 'T' zu' const T & 'ist eine Identitätskonvertierung, da die Referenz * direkt * an das Argument bindet. – TartanLlama