Ich frage nach einem Vorlagentrick, um festzustellen, ob eine Klasse eine bestimmte Elementfunktion einer bestimmten Signatur hat.
Das Problem ähnelt dem hier genannten Problem http://www.gotw.ca/gotw/071.htm, ist jedoch nicht dasselbe: In Sutters Buch beantwortete er die Frage, dass eine Klasse C eine Mitgliedsfunktion bereitstellen MUSS eine bestimmte Signatur, sonst wird das Programm nicht kompiliert. In meinem Problem muss ich etwas tun, wenn eine Klasse diese Funktion hat, sonst "etwas anderes".
Ein ähnliches Problem trat bei boost :: serialization auf, aber ich mag die Lösung nicht: eine Vorlagenfunktion, die standardmäßig eine freie Funktion (die Sie definieren müssen) mit einer bestimmten Signatur aufruft, es sei denn, Sie definieren eine bestimmte Elementfunktion ( in ihrem Fall "serialize", das 2 Parameter eines bestimmten Typs mit einer bestimmten Signatur verwendet, tritt andernfalls ein Kompilierungsfehler auf. Dies bedeutet, sowohl eine aufdringliche als auch eine nicht aufdringliche Serialisierung zu implementieren.
Ich mag diese Lösung aus zwei Gründen nicht:
- Um nicht aufdringlich zu sein, müssen Sie die globale "Serialize" -Funktion im Boost :: Serialization-Namespace überschreiben, damit Sie in Ihrem Kundencode den Namespace Boost und die Namespace-Serialisierung öffnen können!
- Der Stapel, um dieses Durcheinander zu beheben, bestand aus 10 bis 12 Funktionsaufrufen.
Ich muss ein benutzerdefiniertes Verhalten für Klassen definieren, die nicht über diese Elementfunktion verfügen, und meine Entitäten befinden sich in verschiedenen Namespaces (und ich möchte keine globale Funktion überschreiben, die in einem Namespace definiert ist, während ich mich in einem anderen befinde).
Können Sie mir einen Hinweis geben, um dieses Rätsel zu lösen?
Antworten:
Ich bin nicht sicher, ob ich Sie richtig verstehe, aber Sie können SFINAE nutzen, um das Vorhandensein von Funktionen zur Kompilierungszeit zu erkennen. Beispiel aus meinem Code (testet, ob die Klasse die Mitgliedsfunktion size_t used_memory () const hat).
quelle
size_t(std::vector::*p)() = &std::vector::size;
.Hier ist eine mögliche Implementierung, die auf C ++ 11-Funktionen basiert. Es erkennt die Funktion korrekt, auch wenn sie vererbt wurde (im Gegensatz zu der Lösung in der akzeptierten Antwort, wie Mike Kinghan in seiner Antwort feststellt ).
Die Funktion, auf die dieses Snippet testet, heißt
serialize
:Verwendung:
quelle
serialize
selbst eine Vorlage akzeptiert? Gibt es eine Möglichkeit, dieserialize
Existenz zu testen, ohne den genauen Typ einzugeben?Die akzeptierte Antwort auf diese Frage der Introspektion von Compiletime-Member-Funktionen ist zwar zu Recht beliebt, weist jedoch einen Haken auf, der im folgenden Programm beobachtet werden kann:
Errichtet mit GCC 4.6.3, die Programmausgaben
110
- informiert uns , dassT = std::shared_ptr<int>
sich nicht sorgenint & T::operator*() const
.Wenn Sie mit diesem Fall noch nicht vertraut sind, wird ein Blick auf die Definition von
std::shared_ptr<T>
in der Kopfzeile<memory>
Licht ins Dunkel bringen. Wird in dieser Implementierungstd::shared_ptr<T>
von einer Basisklasse abgeleitet, von der es erbtoperator*() const
. Die VorlageninstanziierungSFINAE<U, &U::operator*>
, die das "Finden" des Operators für darstellt,U = std::shared_ptr<T>
wird also nicht stattfinden, dastd::shared_ptr<T>
sie keinoperator*()
eigenes hat und die Vorlageninstanziierung keine "Vererbung" durchführt.Dieser Haken wirkt sich nicht auf den bekannten SFINAE-Ansatz aus, bei dem "The sizeof () Trick" verwendet wird, um lediglich festzustellen, ob
T
eine Elementfunktion vorliegtmf
(siehe z. B. diese Antwort und Kommentare). Aber festzustellen, dass esT::mf
existiert, ist oft (normalerweise?) Nicht gut genug: Möglicherweise müssen Sie auch feststellen, dass es eine gewünschte Signatur hat. Hier punktet die illustrierte Technik. Die mit Zeigern versehene Variante der gewünschten Signatur wird in einen Parameter eines Vorlagentyps eingeschrieben, der erfüllt sein muss,&T::mf
damit die SFINAE-Sonde erfolgreich ist. Diese Technik zur Instanziierung von Vorlagen gibt jedoch die falsche Antwort, wenn sieT::mf
vererbt wird.Eine sichere SFINAE-Technik für die Introspektion während der Kompilierung
T::mf
muss die Verwendung&T::mf
eines Template-Arguments vermeiden , um einen Typ zu instanziieren, von dem die Auflösung von SFINAE-Funktionsvorlagen abhängt. Stattdessen kann die Auflösung der SFINAE-Vorlagenfunktion nur von genau relevanten Typdeklarationen abhängen, die als Argumenttypen der überladenen SFINAE-Testfunktion verwendet werden.Als Antwort auf die Frage, die diese Einschränkung einhält, werde ich zur Erkennung der Kompilierungszeit
E T::operator*() const
, für willkürlicheT
undE
. Das gleiche Muster wird entsprechend angewendet , um nach Signaturen anderer Mitgliedsmethoden zu suchen.In dieser Lösung wird die überladene SFINAE-Sondenfunktion
test()
"rekursiv aufgerufen". (Natürlich wird es überhaupt nicht aufgerufen; es enthält lediglich die vom Compiler aufgelösten Rückgabetypen hypothetischer Aufrufe.)Wir müssen nach mindestens einem und höchstens zwei Informationspunkten suchen:
T::operator*()
überhaupt? Wenn nicht, sind wir fertig.T::operator*()
seine Unterschrift gegebenE T::operator*() const
?Wir erhalten die Antworten, indem wir den Rückgabetyp eines einzelnen Anrufs an bewerten
test(0,0)
. Das wird gemacht von:Dieser Aufruf wird möglicherweise in die
/* SFINAE operator-exists :) */
Überlastung vontest()
oder in die/* SFINAE game over :( */
Überlastung aufgelöst. Es kann nicht zur/* SFINAE operator-has-correct-sig :) */
Überlastung aufgelöst werden, da dieses nur ein Argument erwartet und wir zwei übergeben.Warum kommen wir an zwei vorbei? Einfach, um die Auflösung zum Ausschluss zu zwingen
/* SFINAE operator-has-correct-sig :) */
. Das zweite Argument hat keine andere Bedeutung.Dieser Aufruf von
test(0,0)
wird für den/* SFINAE operator-exists :) */
Fall aufgelöst, dass das erste Argument 0 den ersten Parametertyp dieser Überladung erfüllt, dhdecltype(&A::operator*)
mitA = T
. 0 wird diesen Typ nur für den Fall erfüllen, dass erT::operator*
existiert.Nehmen wir an, der Compiler sagt Ja dazu. Dann geht es weiter
/* SFINAE operator-exists :) */
und es muss der Rückgabetyp des Funktionsaufrufs bestimmt werden, der in diesem Falldecltype(test(&A::operator*))
- der Rückgabetyp eines weiteren Aufrufs an isttest()
.Dieses Mal übergeben wir nur ein Argument, von
&A::operator*
dem wir jetzt wissen, dass es existiert, oder wir wären nicht hier. Ein Aufruf vontest(&A::operator*)
kann entweder zu/* SFINAE operator-has-correct-sig :) */
oder erneut zu aufgelöst werden/* SFINAE game over :( */
. Der Aufruf stimmt/* SFINAE operator-has-correct-sig :) */
nur für den Fall überein,&A::operator*
dass der einzelne Parametertyp dieser Überlastung erfüllt ist, dhE (A::*)() const
mitA = T
.Der Compiler sagt hier Ja, wenn er
T::operator*
die gewünschte Signatur hat, und muss dann erneut den Rückgabetyp der Überladung auswerten. Keine "Rekursionen" mehr: es iststd::true_type
.Wenn der Compiler nicht
/* SFINAE operator-exists :) */
für den Aufruftest(0,0)
oder nicht/* SFINAE operator-has-correct-sig :) */
für den Aufruf wählttest(&A::operator*)
, ist dies in beiden Fällen der Fall/* SFINAE game over :( */
und der endgültige Rückgabetyp iststd::false_type
.Hier ist ein Testprogramm, das die Vorlage zeigt, die die erwarteten Antworten in verschiedenen Fallbeispielen liefert (erneut GCC 4.6.3).
Gibt es neue Mängel in dieser Idee? Kann es generischer gemacht werden, ohne erneut den Haken zu verlieren, den es vermeidet?
quelle
Hier sind einige Verwendungsausschnitte: * Der Mut für all dies ist weiter unten
Suchen Sie nach Mitgliedern
x
in einer bestimmten Klasse. Könnte var, func, class, union oder enum sein:Auf Mitgliedsfunktion prüfen
void x()
:Auf Mitgliedsvariable prüfen
x
:Nach Mitgliederklasse suchen
x
:Auf Mitgliedsgewerkschaft prüfen
x
:Auf Mitgliederliste prüfen
x
:Überprüfen Sie
x
unabhängig von der Signatur, ob ein Mitglied funktioniert :ODER
Details und Kern:
Makros (El Diablo!):
CREATE_MEMBER_CHECK:
CREATE_MEMBER_VAR_CHECK:
CREATE_MEMBER_FUNC_SIG_CHECK:
CREATE_MEMBER_CLASS_CHECK:
CREATE_MEMBER_UNION_CHECK:
CREATE_MEMBER_ENUM_CHECK:
CREATE_MEMBER_FUNC_CHECK:
CREATE_MEMBER_CHECKS:
quelle
Dies sollte ausreichen, wenn Sie den Namen der erwarteten Mitgliedsfunktion kennen. (In diesem Fall kann die Funktion bla nicht instanziiert werden, wenn keine Elementfunktion vorhanden ist (das Schreiben einer Funktion, die ohnehin funktioniert, ist schwierig, da es an einer teilweisen Spezialisierung der Funktionen mangelt. Möglicherweise müssen Sie Klassenvorlagen verwenden.) Auch die Aktivierungsstruktur (welche ähnelt enable_if) könnte auch als Vorlage für den Funktionstyp dienen, den Sie als Mitglied haben möchten.
quelle
Hier ist eine einfachere Darstellung der Antwort von Mike Kinghan. Dadurch werden geerbte Methoden erkannt. Es wird auch nach der genauen Signatur gesucht (im Gegensatz zu jroks Ansatz, der Argumentkonvertierungen ermöglicht).
Ausführbares Beispiel
quelle
using
, um Überladungen aus der Basisklasse zu bringen. Es funktioniert für mich auf MSVC 2015 und mit Clang-CL. Es funktioniert jedoch nicht mit MSVC 2012.Sie können std :: is_member_function_pointer verwenden
quelle
&A::foo
ein Compiler - Fehler, wenn es keine istfoo
überhaupt inA
? Ich habe die ursprüngliche Frage so gelesen, dass sie mit jeder Eingabeklasse funktionieren soll, nicht nur mit solchen, bei denen ein Mitglied benannt istfoo
.Kam selbst mit der gleichen Art von Problem und fand die hier vorgeschlagenen Lösungen sehr interessant ... hatte aber die Anforderung nach einer Lösung, die:
Ich habe einen anderen Thread gefunden , der so etwas vorschlägt, basierend auf einer BOOST-Diskussion . Hier ist die Verallgemeinerung der vorgeschlagenen Lösung als Deklaration von zwei Makros für die Merkmalsklasse nach dem Modell der Klassen boost :: has_ * .
Diese Makros werden zu einer Merkmalsklasse mit dem folgenden Prototyp erweitert:
Was ist also die typische Verwendung, die man daraus machen kann?
quelle
Um dies zu erreichen, müssen wir verwenden:
type_traits
Header möchten wir eintrue_type
oderfalse_type
von unseren Überladungen zurückgebentrue_type
Überlastung, die einen erwartet,int
und diefalse_type
Überlast, die Variadic-Parameter ausnutzt: "Die niedrigste Priorität der Ellipsenkonvertierung bei der Überlastauflösung"true_type
Funktion verwenden wirdeclval
unddecltype
es uns ermöglicht , die Funktion unabhängig von Rückgabetyp Unterschieden oder Überlastungen zwischen Methoden zu erfassen ,Ein Live-Beispiel dafür sehen Sie hier . Aber ich werde es auch unten erklären:
Ich möchte prüfen, ob eine Funktion mit dem Namen vorhanden ist, von
test
der ein konvertierbarer Typ stammtint
. Dann muss ich diese beiden Funktionen deklarieren:decltype(hasTest<a>(0))::value
istrue
(Beachten Sie, dass keine speziellen Funktionen erstellt werden müssen, um mit dervoid a::test()
Überlastung umzugehen. Diesvoid a::test(int)
wird akzeptiert.)decltype(hasTest<b>(0))::value
istrue
(Weilint
konvertierbar indouble
int b::test(double)
akzeptiert wird, unabhängig vom Rückgabetyp)decltype(hasTest<c>(0))::value
isfalse
(c
hat keine benannte Methodetest
, die einen von dort konvertierbaren Typ akzeptiert,int
daher wird dies nicht akzeptiert)Diese Lösung hat zwei Nachteile:
test()
Methode testen möchte ?Daher ist es wichtig, dass diese Funktionen in einem Detail-Namespace deklariert werden. Wenn sie nur mit einer Klasse verwendet werden sollen, sollten sie von dieser Klasse privat deklariert werden. Zu diesem Zweck habe ich ein Makro geschrieben, mit dem Sie diese Informationen abstrahieren können:
Sie könnten dies wie folgt verwenden:
Anschließend aufrufen
details::test_int<a>::value
oderdetails::test_void<a>::value
ergebentrue
oderfalse
zum Zwecke des Inline-Codes oder der Metaprogrammierung.quelle
Um nicht aufdringlich zu sein, können Sie
serialize
dank Koenig Lookup auch den Namespace der zu serialisierenden Klasse oder der Archivklasse eingeben . Weitere Informationen finden Sie unter Namespaces für freie Funktionsüberschreibungen . :-)Das Öffnen eines bestimmten Namespace zum Implementieren einer freien Funktion ist einfach falsch. (z. B. sollten Sie keinen Namespace öffnen
std
, um ihnswap
für Ihre eigenen Typen zu implementieren , sondern stattdessen die Koenig-Suche verwenden.)quelle
In Ordnung. Zweiter Versuch. Es ist in Ordnung, wenn Sie dieses auch nicht mögen, ich suche nach mehr Ideen.
Herb Sutters Artikel spricht über Eigenschaften. Sie können also eine Merkmalsklasse haben, deren Standardinstanziierung das Fallback-Verhalten aufweist. Für jede Klasse, in der Ihre Mitgliedsfunktion vorhanden ist, ist die Merkmalsklasse darauf spezialisiert, die Mitgliedsfunktion aufzurufen. Ich glaube, Herbs Artikel erwähnt eine Technik, um dies zu tun, damit nicht viel kopiert und eingefügt wird.
Wie ich bereits sagte, möchten Sie vielleicht nicht die zusätzliche Arbeit, die mit dem "Markieren" von Klassen verbunden ist, die dieses Mitglied implementieren. In diesem Fall suche ich eine dritte Lösung ....
quelle
Sie scheinen die Detektorsprache zu wollen. Die obigen Antworten sind Variationen davon, die mit C ++ 11 oder C ++ 14 funktionieren.
Die
std::experimental
Bibliothek verfügt über Funktionen, die dies im Wesentlichen tun. Wenn Sie ein Beispiel von oben überarbeiten, könnte es sein:Wenn Sie std :: experimental nicht verwenden können, kann eine rudimentäre Version wie folgt erstellt werden:
Da has_serialize_t tatsächlich entweder std :: true_type oder std :: false_type ist, kann es über eine der gängigen SFINAE-Redewendungen verwendet werden:
Oder durch Verwendung von Versand mit Überlastauflösung:
quelle
Ohne C ++ 11-Unterstützung (
decltype
) könnte dies funktionieren:SSCCE
Wie es hoffentlich funktioniert
A
,Aa
UndB
sind die clases in Frage,Aa
die besonderen sein , dass erbt das Element wir suchen.In der
FooFinder
dietrue_type
undfalse_type
ist der Ersatz für die entsprechenden C ++ 11 Klassen. Auch für das Verständnis der Template-Meta-Programmierung enthüllen sie die Grundlage des SFINAE-Trickgrößen.Das
TypeSink
ist eine Vorlage struct , die später verwendet wird , um das Integralergebnis des versenkensizeof
Bediener in eine Vorlage Instanziierung einen Typ zu bilden.Die
match
Funktion ist eine andere SFINAE-Vorlage, die ohne generisches Gegenstück verbleibt. Es kann daher nur instanziiert werden, wenn der Typ seines Arguments mit dem Typ übereinstimmt, auf den es spezialisiert wurde.Beide
test
Funktionen bilden zusammen mit der Enum-Deklaration schließlich das zentrale SFINAE-Muster. Es gibt eine generische, die eine Ellipse verwendet, die dasfalse_type
und ein Gegenstück mit spezifischeren Argumenten zurückgibt , um Vorrang zu haben.Um die
test
Funktion mit einem Vorlagenargument von instanziieren zu können, mussT
diematch
Funktion instanziiert werden, da ihr Rückgabetyp erforderlich ist, um dasTypeSink
Argument zu instanziieren . Die Einschränkung besteht darin&U::foo
, dass das Einschließen in ein Funktionsargument nicht innerhalb einer Vorlagenargumentspezialisierung referenziert wird, sodass die Suche nach geerbten Mitgliedern weiterhin stattfindet.quelle
Wenn Sie Facebook-Torheit verwenden, sind diese sofort einsatzbereit, um Ihnen zu helfen:
Obwohl die Implementierungsdetails mit der vorherigen Antwort identisch sind, ist die Verwendung einer Bibliothek einfacher.
quelle
Ich hatte ein ähnliches Bedürfnis und stieß auf dieses SO. Hier werden viele interessante / leistungsstarke Lösungen vorgeschlagen, die jedoch nur für einen bestimmten Bedarf etwas lang sind: Ermitteln Sie, ob eine Klasse eine Mitgliedsfunktion mit einer präzisen Signatur hat. Also habe ich etwas gelesen / getestet und mir meine Version ausgedacht, die von Interesse sein könnte. Es erkennt:
mit einer genauen Unterschrift. Da ich keine Signatur erfassen muss (was eine kompliziertere Lösung erfordern würde), passt diese zu mir. Grundsätzlich wurde enable_if_t verwendet .
Ausgabe :
quelle
Aufbauend auf der Antwort von jrok habe ich die Verwendung verschachtelter Vorlagenklassen und / oder -funktionen vermieden.
Wir können die obigen Makros wie folgt verwenden:
Vorschläge sind willkommen.
quelle