sfinae und iterator_traits
-
das war etwas unglücklich formuliert. der typ existiert stets, U::value_type aber eben nicht immer (sofern mal keine überladung von iterator_traits greift).
was ich tun möchte ist zwei versionen einer funktion anzubieten (so quasi überladen), abhängig davon was der dritte parameter ist. naiv sieht das in etwa so aus:
template<typename T, typename U> void f(T, T, U) { } template<typename T> void f(T, T, typename std::iterator_traits<T>::value_type) { }wenn U implizit in std::iterator_traits<T>::value_type konvertierbar ist, so möchte ich die zweite version benutzen, ansonsten die erste.
dies funktioniert jedoch nicht so einfach, da U und std::iterator_traits<T>::value_type "gleichrangig" sind (was ich auch nicht verstehe btw; wenn der parameter vom typen int oder so wäre, dann würde dieser selbstverständlich vorgezogen werden; hier beim abhängigen namen ist das nicht der fall...). wenn das gezeigte konstrukt aus dem op funktionieren würde, so könnte ich mit trivialen mitteln eine lösung schreiben (erst prüfen ob der typ existiert, dann prüfen ob implizit konvertierbar).
-
dies funktioniert jedoch nicht so einfach, da U und std::iterator_traits<T>::value_type "gleichrangig" sind (was ich auch nicht verstehe btw; wenn der parameter vom typen int oder so wäre, dann würde dieser selbstverständlich vorgezogen werden; hier beim abhängigen namen ist das nicht der fall...).
~~Eigentlich sollte das zweite Template spezialisierter sein. Offenbar gibt es irgendwo in C++98 eine Regel die besagt dass partial ordering non-deduced contexts nicht beachtet.~~Nein, auch laut C++11 ist keines spezialisierter als das andere, schließlich ist die Deduzierung in beide Richtungen erfolgreich. Eigentlich bescheuert.
Nehmen wir einfach mal an dass es nur zwei Arten von
Ts gibt, für dieiterator_traits::value_typegültig ist: Klassen mit einem Membervalue_type, und Zeiger. Dann funktioniert das folgende:#include <iostream> template <int i> struct choice : choice<i+1> {}; template <> struct choice<10> {}; template <typename T> void f_impl( choice<0>, T, T, typename T::value_type ) {std::cout << __PRETTY_FUNCTION__ << '\n';} template <typename T> struct identity {typedef T type;}; template <typename T> void f_impl( choice<1>, T*, T*, typename identity<T>::type ) // non-deduced context für Parameter 3 {std::cout << __PRETTY_FUNCTION__ << '\n';} template <typename T, typename U> void f_impl( choice<2>, T, T, U ) {std::cout << __PRETTY_FUNCTION__ << '\n';} template <typename T, typename U> void f(T a, T b, U u) { f_impl(choice<0>(), a, b, u); } int main() { int const*a; f(a,a, 7); f(a,a, "aewf"); std::string str; f(str.end(), str.end(), '7'); }
-
Arcoth schrieb:
hast du das etwa abgeschrieben?
rhetorische Frage, oder?
Arcoth schrieb:
Denn es ist die falsche Erklärung, schließlich existiert die Regel genau so in C++11 und mit -std=c++11 funktioniert es plötzlich, sowohl mit Clang als auch GCC.
Da dein clang vermutlich die Header von gcc verwendet, geht es nur um einen Compiler. Seit Version 4.6 macht gcc etwas Seltsames: das allgemeine iterator_traits<T> ist dort leer für T, die keinen Typ T::iterator_category haben (und gleichzeitig c++0x/c++11 aktiv ist). Soweit erkennbar, widerspricht das dem Wortlaut des Standards. Kurze Suche im gcc-Bugtracker hat nichts gefunden und ich habe keine Lust, die commits durchzusehen.
Das Problem lässt sich reduzieren auf
template <typename T> struct foo { typedef typename T::type type; }; int main() { sizeof(foo<int>); } // ill-formedund ist weder in C++03 noch C++11 zulässig.
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.1/include/g++-v4/bits/stl_iterator_base_types.h
#if __cplusplus >= 201103L _GLIBCXX_HAS_NESTED_TYPE(iterator_category) template<typename _Iterator, bool = __has_iterator_category<_Iterator>::value> struct __iterator_traits { }; template<typename _Iterator> struct __iterator_traits<_Iterator, true> { typedef typename _Iterator::iterator_category iterator_category; typedef typename _Iterator::value_type value_type; typedef typename _Iterator::difference_type difference_type; typedef typename _Iterator::pointer pointer; typedef typename _Iterator::reference reference; }; template<typename _Iterator> struct iterator_traits : public __iterator_traits<_Iterator> { }; #else template<typename _Iterator> struct iterator_traits { typedef typename _Iterator::iterator_category iterator_category; typedef typename _Iterator::value_type value_type; typedef typename _Iterator::difference_type difference_type; typedef typename _Iterator::pointer pointer; typedef typename _Iterator::reference reference; }; #endif
-
statt iterator_traits könnte man hier evtl. auch einfach direkt einen passenden Ausdruck verwenden, ungefähr so:
template <bool b, typename T = void> struct enable_if { typedef T type; }; template <typename T> struct enable_if<false, T> {}; template <typename T> T declval(); template<typename T, typename U> void f_(T, T, U, ...) { } template<typename T, typename U> typename enable_if<sizeof((*declval<T>())=declval<U>())!=0>::type f_(T, T, U, int) { } template<typename T, typename U> void f(T a, T b, U c) { f_(a,b,c,0); }vorausgesetzt, value_type verhält sich einigermaßen normal (u.a. kein void-return bei Zuweisung).
-
Da dein clang vermutlich die Header von gcc verwendet
Hast du überhaupt mal libc++' Implementierung getestet? Mit der kompiliert der im OP gezeigte Code einwandfrei. Zumindest auf meiner Maschine mit libc++ 3.5. Dass gleich beide Implementierungen nicht standardkonform sind konnte ich gar nicht glauben und mir alle Defekt-Reports zum Thema deduction failures angeschaut. Nächstes mal zweifle ich zuerst an der Implementierung als an meinem Verständnis des Standards.vorausgesetzt, value_type verhält sich einigermaßen normal (u.a. kein void-return bei Zuweisung).
sizeof(..., 0)?template <typename T> T declval();
Offenbar die falsche Deklaration. Nicht jeder Zuweisungsoperator kann rvalues nehmen.
Edit: Oder doch?P.S.: Wieso sollte dein Code für ältere Compiler eigentlich funktionieren? Können die expression SFINAE? Laut dem Paper für C++11 können ältere (das heißt, die hier relevanten) das nämlich nicht. Du hast sicherlich GCC 4.0 installiert, wie sieht's damit aus?
-
Arcoth schrieb:
Hast du überhaupt mal libc++' Implementierung getestet? Mit der kompiliert der im OP gezeigte Code einwandfrei. Zumindest auf meiner Maschine mit libc++ 3.5. Dass gleich beide Implementierungen nicht standardkonform sind konnte ich gar nicht glauben und mir alle Defekt-Reports zum Thema deduction failures angeschaut. Nächstes mal zweifle ich zuerst an der Implementierung als an meinem Verständnis des Standards.das erfreut mich dass der code doch korrekt wäre (das ist doch die implikation oder?).
Arcoth schrieb:
sizeof(..., 0)?hier hast du vermutlich eher ,1 gemeint (ansonsten muss man auch die ungleichung dahinter ändern).
Arcoth schrieb:
Offenbar die falsche Deklaration. Nicht jeder Zuweisungsoperator kann rvalues nehmen.
Edit: Oder doch?seh ich auch so. die korrekte deklaration von declval ist mit unqualifizierter lvalue-referenz.
Arcoth schrieb:
P.S.: Wieso sollte dein Code für ältere Compiler eigentlich funktionieren? Können die expression SFINAE? Laut dem Paper für C++11 können ältere (das heißt, die hier relevanten) das nämlich nicht. Du hast sicherlich GCC 4.0 installiert, wie sieht's damit aus?
so aus neugier und unwissenheit: wäre expression sfinae nach c++03 korrekt? ab und zu hat expression-sfinae bei mir sichtbarkeit von membern / methoden ignoriert.

-
das erfreut mich dass der code doch korrekt wäre (das ist doch die implikation oder?).
Ich fürchte nicht. Die Implikation ist dass die falschen Implementierungen von sowohl Clang als auch GCC mit dem Code momentan funktionieren. campers Lösung ist wahrscheinlich die die deinem Problem am nahsten kommt.
hier hast du vermutlich eher ,1 gemeint (ansonsten muss man auch die ungleichung dahinter ändern).
sizeofnimmt die Größe des Ausdrucktyps, welchen Wert dieser hat ist völlig egal.so aus neugier und unwissenheit: wäre expression sfinae nach c++03 korrekt?
Ja. Es gab aber lange Zeit Zweifel ob das der Fall ist oder nicht, und auch aus dem Grund haben einige Compiler das nicht implementiert. N2634 erklärt das.
-
Arcoth schrieb:
Da dein clang vermutlich die Header von gcc verwendet
Hast du überhaupt mal libc++' Implementierung getestet? Mit der kompiliert der im OP gezeigte Code einwandfrei. Zumindest auf meiner Maschine mit libc++ 3.5. Dass gleich beide Implementierungen nicht standardkonform sind konnte ich gar nicht glauben und mir alle Defekt-Reports zum Thema deduction failures angeschaut. Nächstes mal zweifle ich zuerst an der Implementierung als an meinem Verständnis des Standards.nicht mit libc++ getestet, da nicht installiert. Ist ja kein integraler Bestandteil des Compilers.
Arcoth schrieb:
vorausgesetzt, value_type verhält sich einigermaßen normal (u.a. kein void-return bei Zuweisung).
sizeof(..., 0)?gute Idee.
Arcoth schrieb:
template <typename T> T declval();
Offenbar die falsche Deklaration. Nicht jeder Zuweisungsoperator kann rvalues nehmen.
Zutreffend, allerdings ist der Typ dann in C++03 vermutlich nicht mehr Containerkompatibel. Es ging mir hier nur um eine Prototypen. ggf. muss man eben auf declval<T&>() zurückgreifen.
Arcoth schrieb:
P.S.: Wieso sollte dein Code für ältere Compiler eigentlich funktionieren? Können die expression SFINAE? Laut dem Paper für C++11 können ältere (das heißt, die hier relevanten) das nämlich nicht. Du hast sicherlich GCC 4.0 installiert, wie sieht's damit aus?
mit
int main() { int* p = 0; void* q = 0; f(p,p,0); f(q,q,0); }3.4.6 - ICE
4.0.4 - 4.3.6: error: ‘void*’ is not a pointer-to-object type
4.4.7 - 4.6.4: sorry, unimplemented: mangling modop_expr
ab 4.7. geht es dann...
-
Hier ist der gcc-Bug, der zu der Änderung in der Standardbibliothek geführt hat. Falls sich libc++ hier genauso verhält, dann vermutlich aus den gleichen Gründen.
-
Was genau kann GCC 4.4.7 nicht mangeln? Anscheinend die Zuweisung:
<a href= schrieb:
Mark Mitchell">It's an assignment operator (including compound assignments) while parsing a template.
camper schrieb:
nicht mit libc++ getestet, da nicht installiert.
Wie lange dauert es libc++ auf Gentoo zu installieren? Eine Minute? Ein Vorteil wäre eine breitere Analyse gängiger Implementierungen.
-
Anscheinend war name mangling das einzige Problem für GCC >= 4.4.7.
template <int> struct void_t {typedef void type;}; template <typename T> T declval(); #include <iostream> template <typename T, typename U, typename=void > struct check { void operator()(T,T,U){std::cout << '0';} }; template <typename T, typename U> struct check<T, U, typename void_t<sizeof((*declval<T>())=declval<U>(),0)>::type> { void operator()(T,T,U){std::cout << '1';} };; template<typename T, typename U> void f(T a, T b, U c) { check<T, U>()(a,b,c); } int main() { int* p = 0; void* q = 0; f(p,p,0); f(q,q,0); }(Auf GCC Godbolt getestet; Zwar keine Ausgabe direkt verfügbar, der Assemblercode ist aber aussagekräftig
movb $49, 15(%rsp) movq %rbx, %rsi call std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) movq %rbx, %rsi movl $1, %edx movl std::cout, %edi movb $48, 15(%rsp) call std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) xorl %eax, %eax addq $16, %rsp popq %rbx)
-
Arcoth schrieb:
template <typename T, typename U> struct check<T, U, typename void_t<sizeof((*declval<T>())=declval<U>(),0)>::type> { void operator()(T,T,U){std::cout << '1';} };Interessante Idee. Funktioniert ab g++ 4.4 und 3.4.6. steigt zumindest nicht mehr mit einem ice aus.
Nachdem ich das überschlafen habe, bin ich jetzt der Ansicht, dass das Verhalten von gcc und clang vom Standard gedeckt ist:
Die Instantiierung von iterator_traits für Typen, die die entsprechenden typedefs nicht haben und für die iterator_traits nicht spezialisiert wurde, macht das jeweilige Programm formal ill-formed.
24.4.1/2 ist damit implizit auch eine Spezifikation der Anforderungen, die an das Templateargument gestellt werden, und wenn diese nicht erfüllt werden, greift 17.6.4.8/1, also UB.