sfinae und iterator_traits


  • Mod

    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-formed
    

    und 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
    

  • Mod

    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).


  • Mod

    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. 😕


  • Mod

    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).

    sizeof nimmt 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.


  • Mod

    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...


  • Mod

    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.


  • Mod

    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.


  • Mod

    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
    

    )


  • Mod

    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.


Anmelden zum Antworten