5

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.

+0

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

+0

@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

+0

@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

Antwort

6

Da die zweite Überlast die oberste const innerhalb der const T ablegt, wird sie während der Argumentableitung auf T* aufgelöst. Die erste Überladung ist eine schlechtere Übereinstimmung, da sie in S<int, char> const* aufgelöst wird, was eine const-qualification-Konvertierung erfordert.

Sie müssen const vor Ihrer Variablen s hinzuzufügen, um für die speziellere Überlastung treten:

#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;} 

template<class T> 
constexpr bool isS(const T) {return false;} 

int main() { 
    S<int,char> const s{}; // add const here 
    std::cout<<isS(&s)<<std::endl; 
    return 0; 
} 

Live Example

Ändern der ersten Überlastung zu einem const S<A,B>&, geben die richtige Ergebnis, weil eine Identitätskonvertierung anstelle einer Qualifikationsanpassung stattfindet.

13.3.3.1.4 Referenzbindung [over.ics.ref]

1 Wenn ein Parameter des Referenztyps direkt bindet (8.5.3) für einen Argumentausdruck ist die implizite Konvertierungssequenz die Identitätskonvertierung, es sei denn, der Argumentargument hat einen Typ, der eine abgeleitete Klasse des -Parametertyps ist. In diesem Fall ist die implizite Konvertierungssequenz ein abgeleitet von Basiskonversion (13.3.3.1).

Hinweis: wenn im Zweifel über solche Argument Abzug Spiele, ist es praktisch, die __PRETTY_FUNCTION__ Makro, das (auf gcc/Klirren) zu verwenden, finden Sie weitere Informationen zu den abgeleiteten Typen der ausgewählten Vorlage geben. Sie können bestimmte Übersteuerungen auskommentieren, um zu sehen, wie sich dies auf die Überladungsauflösung auswirkt. Siehe hierzu live example.

+0

Danke dafür, aber bitte sehen Sie meinen Kommentar oben (unter der Frage). – Ose

+0

@Osse tnx, aktualisiert. – TemplateRex

+0

Toll, danke für den Hinweis auf den Standard! – Ose

3

Ihre zweite Version gibt nicht die Antwort, die Sie erwarten, da die erste Version von isS eine implizite Konvertierung erfordert, während die zweite nicht.

template<class A, class B> 
constexpr bool isS(const S<A,B>*); 

template<class T> 
constexpr bool isS(const T); 

S<int,char> s; 
isS(&s); 

Beachten Sie, dass &s vom Typ S<int,char>*. Die erste isS sucht nach einer const S<int,char>*, so dass der Zeiger eine Konvertierung benötigt. Die zweite isS ist eine direkte Übereinstimmung.


Wenn Sie sich um dieses Muster oft finden, könnten Sie es verallgemeinern, etwa so:

template<template<typename...> class TT, typename T> 
struct is_specialization_of : std::false_type {}; 

template<template<typename...> class TT, typename... Ts> 
struct is_specialization_of<TT, TT<Ts...>> : std::true_type {}; 

Sie dann überprüfen, ob ein Typ eine Spezialisierung von S ist wie folgt:

is_specialization_of<S, decltype(s)>::value 
+0

Danke dafür, aber bitte sehen Sie meinen Kommentar oben (unter der Frage). Auch +1 für die Verallgemeinerung - daran hatte ich nicht gedacht! – Ose

+0

Ich habe zwei Mängel Ihrer Verallgemeinerung bemerkt. Eines ist, dass es const TT ist keine Spezialisierung von TT . Wenn dies wünschenswert ist (wie in meinem Fall), muss eine zweite Spezialisierung für const TT hinzugefügt werden. Der andere Mangel ist, dass es nicht mit Klassenvorlagen verwendet werden kann, die eine Mischung aus Typ- und Nicht-Typ-Parametern haben (was eigentlich die meisten meiner Anwendungsfälle sind). Ich sehe keine Möglichkeit, es weiter zu verallgemeinern, um diesen Fall zu behandeln ... – Ose

+0

Der erste Fehler kann gelöst werden, indem man ihn in einen Template-Alias ​​einfügt, der 'std :: remove_const' zuerst auf dem Typ oder sogar' std aufruft: : zerfallen, um alles zu entfernen. Der zweite ist ein Problem, das ich schon mal kennengelernt habe und für das es immer noch keine gute Lösung gibt. – TartanLlama