Template resolution
-
Also ich habe hier das folgende Konstrukt:
template< typename T, typename Enable = void > struct converter; template< typename T > struct converter< T, enable_if< irgendwas > > { }; template< typename T, typename Enable > struct converter { // allgemeiner Fall }; // Instanziierung converter< irgendwas dass das enable_if anwirft > ....Und nun wird die Spezialisierung mit dem enable_if getroffen, wenn das enable_if eben sein OK gibt. Ich habe im Moment nur eine diffuses Verständnis dafür, was die genaue Regel dafür ist, dass das Template "spezialisierter" für den Typ ist als das allgemeine letzte, dabei muss er sich das Enable-If doch selber herleiten und es wurde bei der der Instanzierung gar nicht benannt.
Wie lautet da die genaue Regel? "Wenn irgendwie auf welchem Weg auch immer eine Spezialisierung hergeleitet werden kann, die kompiliert, dann ist diese der unspezialisierten Fassung vorzuziehen."?
Mannmann, offensichtlich ist irgendwie was anderes, vor allem wenn die drei Definitionen dann auch noch auf unterschiedliche Dateien verteilt liegen.
-
decimad schrieb:
Also ich habe hier das folgende Konstrukt:
template< typename T > struct converter< T, enable_if< irgendwas > > { };Das soll sicher sein
template< typename T > struct converter< T, typename enable_if< irgendwas_von_T_abhängendes >::type > { };Der Unterschied ist wichtig, weil das Ganze nur funktioniert, wenn aus dem enable_if-Argument das T nicht deduziert werden kann.
Die Regeln für die Entscheidung, welche einer Reihe partieller Spezialisierungen eines Klassentemplates jeweils zu verwenden sind, sind die Gleichen, die bei der Überladungsauflösung von Funktionstemplates angewandt werden - falls du damit vertraut bist, kann das ganze analog so umgeschrieben werden:template< typename T, typename Enable = void > struct converter; // | | // wird zu // \ / \ / template <typename T_Fun, typename Enable_Fun = int > // int statt void, weil void kein Funktionsparameter sein kann, hat sonst keine weitere Bedeutung struct converter_fun(T_Fun, Enable_Fun = 0); template< typename T > struct converter</*|*/ T, typename enable_if< irgendwas >::type > {// | | | };// | | | // wird zu | | | template <typename T_Fun>//| | struct converter_fun( T_Fun, typename enable_if<irgendwas<T>,int>::type> = 0);Die Spezialisierung die für ein converter<foo> verwendet wird ist dann die, die äquivalent für ein
converter_fun(foo{}) anzuwenden wäre (mit der zusätzlichen Regel, das eine partielle Spezialisierung immer besser als das Primärtemplate ist).decimad schrieb:
"Wenn irgendwie auf welchem Weg auch immer eine Spezialisierung hergeleitet werden kann, die kompiliert, dann ist diese der unspezialisierten Fassung vorzuziehen."?
Das ist auf jedenfall zutreffend.
decimad schrieb:
Mannmann, offensichtlich ist irgendwie was anderes, vor allem wenn die drei Definitionen dann auch noch auf unterschiedliche Dateien verteilt liegen.
Bei dir gibt es nur 2 Definitionen und eine zus. Deklaration.
Stark verkürzt:
1. Um zu ermitteln, ob eine bestimmte partielle Spezialisierung verwendet werden kann, für ein bestimmtes Argumenttupel A, wird anhand der Argumentliste der partiellen Spezialisierung und den konkreten Argumenten aus A versucht, die zugehörigen Templateargumente des Primärtemplates (also das, was dem Schlüsselwort template folgt), zu ermitteln. Ist das erfolgreich, werden diese deduzierten Argumente in die Argumentliste der partiellen Spezialisierung eingesetzt und das Ergebnis mit A verglichen. Stimmt dieses mit A überein, so ist diese partielle Spezialisierung für A verwendbar.2. Eine partielle Spezialisierung X ist mindestens so spezialisiert wie eine Spezialisierung Y, wenn man für A die Argumentliste von X verwendet und dann der Test der Verwendbarkeit von Y für dieses A (siehe Schritt 1) positiv ist.
Einfache Faustregel (finde ich einfacher in Gedanken nachzuvollziehen, Mengenlehre - man kann exotische Gegenbeispiele konstruieren):
X ist spezialisierter als Y wenn für jedes Tupel A für das X in Frage kommt auch Y anwendbar wäre und umgekehrt mindestens ein Tupel existiert, für das Y nicht aber X in Frage kommt.Praktisch ist das wahrscheinlich mit Beispielen besser zu erläutern.
-
template< typename T, typename Enable = void > struct converter; // wird zu template <typename T_Fun, typename Enable_Fun = int > // int statt void, weil void kein Funktionsparameter sein kann, hat sonst keine weitere Bedeutung struct converter_fun(T_Fun, Enable_Fun = 0); template< typename T > struct converter< T, typename enable_if< irgendwas >::type > { }; // wird zu template <typename T_Fun, typename Enable_Fun = int> struct converter_fun(T_Fun, int = typename enable_if<irgendwas,int>::type{});Das ist doch Unsinn! Irre ich mich, oder sollte das nach 14.5.5.2 nicht so aussehen:
template< typename T, typename Enable = void > struct converter; // wird zu template <typename T, typename Enable = void > struct converter_fun( converter<T, Enable> ); template< typename T > struct converter< T, typename enable_if< irgendwas >::type > {}; // wird zu template <typename T> struct converter_fun( converter<T, typename enable_if<irgendwas>::type> );
-
Arcoth schrieb:
Das ist doch Unsinn!
Stimmt. Passiert, wenn man beim Schreiben unterbrochen wird. Normalerweise lese ich sowas erst nochmal nach.
Ist normalerweise auch nichts, was im Kopf behalten muss, Faustregeln reichen fast immer aus.
-
Hallo camper,
vielen Dank erstmal für die Ausführlichkeit, das macht es alles viel klarer.
Ich hatte hier bis eben folgendes stehen:template <typename T> struct default_converter < T, typename std::enable_if< detail::is_integer_parameter<T>::value > > : integer_converter< T >Und das hat irgendwie in 95% der Fälle funktioniert, war das vielleicht ein Fehler von VC++?
Vor allem hatte ich (siehe Post im "Rund um die Programmierung") damit das Problem, dass in meiner Veränderung
template <typename T> struct default_converter < T, typename std::enable_if< detail::is_integer_parameter<T> > > : integer_converter< typename detail::disallow_nonconst_reference<T>::type >, wobei in dem disallow_nonconst_reference ein static_assert verborgen ist, das static_assert verschluckt wurde, und die ganze Sache durch SFINAE veworfen wurde.
Aber
template <typename T> struct default_converter < T, typename std::enable_if< detail::is_integer_parameter<T>::value >::type > : integer_converter< typename detail::disallow_nonconst_reference<T>::type >funktioniert laut VC++-Ergebnis jetzt genau so, wie ich es mir von Anfang an gewünscht hatte, das static_assert vom disallow_nonconst_reference springt an, anstatt dass die Spezialisierung verworfen wird und das unspezialisierte Template ausgewählt wird. Ich hatte schon befürchtet, dieser Enable-If-Trick würde das verhindern und ich müsste das gesamte System umstellen.

-
In dem 2. Beispiel fehlt das "::value", das habe ich hier verschluckt, sorry.
-
::type
ist wichtig. Der Type enable_if<x, y> existiert ja immer, für irgendwelche x,y.
Member type dagegen nur, wenn x wahr ist.
-
Ja genau! Wenn ich mir das jetzt halt nach Deinen Ausführungen so anschaue, kann ich mir überhaupt nicht vorstellen, wie das überhaupt mal halbwegs funktioniert haben soll. Aber ich habe bei dem fehlerhaften Konstrukt tatsächlich mit Typen, die kein "integer argument" waren, das unspezialisierte Template getroffen.

-
decimad schrieb:
Ja genau! Wenn ich mir das jetzt halt nach Deinen Ausführungen so anschaue, kann ich mir überhaupt nicht vorstellen, wie das überhaupt mal halbwegs funktioniert haben soll. Aber ich habe bei dem fehlerhaften Konstrukt tatsächlich mit Typen, die kein "integer argument" waren, das unspezialisierte Template getroffen.

Vielleicht existiert ja detail::is_integer_parameter<T>::value nicht in allen Fällen? Sonst minimales Beispiel bitte.
-
Ja, ich bin schon dabei, eines zu konstruieren, weil ich total baff bin... Vielleicht schlägt das aber Fehl und irgendwas anderes ist hier total faul. is_integer_parameter<T>::value existiert auf jeden Fall immer, soviel kann ich sagen.
-
Jetzt wird es mir klar.
Aus absolut nachzuvollziehenden Gründen habe ich niemals die Spezialisierung getroffen (schließlich war enable_if<...> ja nicht "void") und das während der Testfälle, bei denen ich ja sicherstellen wollte, dass ich die Spezialisierung nicht treffe, bzw. dass die Spezialisierung mit static_assert fehlschlagen solle. Dabei konnte mir natürlich auch nicht auffallen, dass ich sie auch niemals in den Fällen getroffen hätte, in denen der Typ ein "akzeptierter" ist.
Jetzt frage ich mich nur, wie lange dieses ::type da inzwischen fehlte Oo. Weil ich noch vor ein paar Tagen auf jeden Fall die Spezialisierung getroffen hatte. Mannmannmann. Ditsch. Ditsch. Ditsch.
-
Und jetzt verstehe ich auch den SFINAE-Trick in diesem Fall. enable_if defaulted den zweiten Typ-Parameter auch zu void (vermutlich weil man das oft als Rückgabetyp von Funktionen hernimmt, oder als Pseudo-Argument zu eben solchen?), das heißt, der Template-Prozessor will "T, void" gegen "T, enable_if< bedindung, void >::type" matchen und sieht, das passt, oder aber es gibt kein type, dann schlägt SFINAE zu.