Implementierung von is_callable



  • Arcoth schrieb:

    Wie kommste drauf?

    Mark2015 schrieb:

    typedef 
            typename std::conditional< 
            std::is_function<T>::value, // false für Lambdas und für unions
            std::true_type, 
            typename std::conditional< 
                 std::is_class<T>::value, // false für unions und unspezifiziert (?) für Lambdas
                 is_callable_impl<T>, 
                 std::false_type 
            >::type 
            >::type type;
    

    Der Code funktioniert (MSVC 2013, GCC) und tut was er soll.

    Vielleicht habe ich ungewollt den Eindruck erweckt, dass ich mich auf die unteren Listings bezogen habe.

    Mark2015 schrieb:

    template<typename C, class = typename std::enable_if<std::is_same<void(Fallback::*)(), decltype(&C::operator())>::value>::type>
    	static no test(int);
    

    Diese Version funktioniert unter MSVC und GCC ebenso. Da wäre auch schon meine erste Frage: Warum muss man an dieser Stelle 'decltype' schreiben? Schreibt man es nicht, dann kompiliert es nur unter Visual Studio, jedoch nicht unter GCC.

    Ja, musst du. Template-Signatur von std::issame :

    template< class T, class U >
    struct is_same;
    

    Der zweite Template-Parameter erwartet also einen Typen. &C::operator() ist jedoch ein Methodenzeiger und ein R-Value, nicht jedoch ein Typ.


  • Mod

    Eigentlich müsste der is_same-Test doch überflüssig sein, ebenso wie der Test auf Fallback im Original.
    Der operator existiert genau dann nicht in T, wenn der Zeiger auf operator() in Derived überhaupt eindeutig gebildet werden kann (und dann ist es notwendigerweise der aus Fallback)
    Also

    template<typename C, class = typename std::enable_if<&C::operator()>::type>
        static no test(int);
    

    ungestet, evtl. Denkfehler drin.



  • camper schrieb:

    ...

    Methodenzeiger können nicht während der Compiletime in bool s konvertiert werden: http://ideone.com/5nrbIE
    Im Weiteren gab es bei einem ähnlichen Konstrukt ein Problem, falls der Operator nicht eindeutig war (selbe Signatur aus mehreren Basisklassen). Teste ich vll. zuhause dann.

    Arcoth schrieb:

    closure types sind Klassen.

    Das wusste ich nicht, danke. Ich wusste bloss, dass der Standard zu Closures nicht sonderlich viel sagt.


  • Mod

    std::is_class<T>::value, // false für unions und unspezifiziert (?) für Lambdas
    

    closure types sind Klassen.


  • Mod

    ungestet, evtl. Denkfehler drin.

    Neben falschem Rueckgabetyp ein peinlicher Fluechtigkeitsfehler

    [temp.arg.nontype]/5 schrieb:

    For a non-type template-parameter of integral or enumeration type, conversions permitted in a converted constant expression (5.19) are applied.

    [expr.const]/3 schrieb:

    A converted constant expression of type T is a literal constant expression, implicitly converted to type T , where the implicit conversion (if any) is permitted in a literal constant expression and the implicit conversion sequence contains only user-defined conversions, lvalue-to-rvalue conversions (4.1), integral promotions (4.5), and integral conversions (4.7) other than narrowing conversions (8.5.4).

    Boolean conversions sind nicht beinhaltet, daher muss da noch ein Cast hin.


  • Mod

    Arcoth schrieb:

    ungestet, evtl. Denkfehler drin.

    Neben falschem Rueckgabetyp ein peinlicher Fluechtigkeitsfehler

    Besonders peinlich ist mir das nicht 😉
    und wieso falscher Rückgabetyp?

    Vergleich != nullptr fände ich and dieser Stelle schöner als einen expliten Cast.


  • Mod

    Ich wusste bloss, dass der Standard zu Closures nicht sonderlich viel sagt.

    Doch, der sagt darueber sogar sehr, sehr viel.

    und wieso falscher Rückgabetyp?

    Warum gibst du "Nein" zurueck wenn die Klasse callable ist?

    camper schrieb:

    Besonders peinlich ist mir das nicht 😉

    Scheisse, du bist so cool 😡

    Vergleich != nullptr fände ich and dieser Stelle schöner als einen expliten Cast.

    Und ich faende ein Klassentemplate sowieso passender, aber

    template<typename C, void* = &C::operator()>
    static yes test(int);
    

    geht auch
    Edit: Nein, geht natuerlich nicht.


  • Mod

    Das kuerzeste ist wahrscheinlich

    template<typename C, int = sizeof(&C::operator())>
    static yes test(int);
    

  • Mod

    Arcoth schrieb:

    Warum gibst du "Nein" zurueck wenn die Klasse callable ist?

    Tue ich doch nicht. Ich gebe nein zurück, weil der Memberzeiger &C::operator() formbar ist, was Eindeutigkeit voraussetzt. Die ist nur gegeben, falls der aus Fallback geerbte Operator der Einzige ist, also nichts aus T geerbt wurde. Wurde nichts aus T geerbt, ist T offenbar nicht callable...


  • Mod

    Ah, ich haette vielleicht den Code lesen sollen anstatt anzunehmen dass es nur unnoetiger Boilerplaite ist...



  • Danke für eure Antworten un euer Interesse.

    Der Hinweis zu dem Methodenzeiger und R-Value war hilfreich :).
    Für unions habt ihr natürlich recht, man vergisst doch immer wieder einen Randfall ;). Allerdings können unions keine Basisklasse aufweisen. Da müsste man sich etwas anderes ausdenken.

    Ich würde gerne noch auf diese Variante zu sprechen kommen, wo wahrscheinlich bei mir ein Denkfehler liegt.

    template<typename C, class R = typename std::conditional<std::is_same<void(Fallback::*)(), decltype(&C::operator())>::value, no, yes >::type>
        static R test(...);
    

    Warum ist das nicht möglich?


  • Mod

    Na weil, falls die Klasse die der Templateparameter bezeichnet einen `operator()` hat, der Ausdruck &C::operator() ill-formed ist (weil dann mehrere operator()s in Derives Scope sind). Genau dass ist ja der Trick, und deswegen ueberlaedt man mit SFINAE.


  • Mod

    Mark2015 schrieb:

    Ich würde gerne noch auf diese Variante zu sprechen kommen, wo wahrscheinlich bei mir ein Denkfehler liegt.

    template<typename C, class R = typename std::conditional<std::is_same<void(Fallback::*)(), decltype(&C::operator())>::value, no, yes >::type>
        static R test(...);
    

    Warum ist das nicht möglich?

    Weil der is_same-Test nicht das testet, worauf es ankommt. Eigentlich habe ich das schon weiter oben beantwortet.

    Nebenbei: als ich den Code das Erste mal gelesen hatte, musste ich erst mal nachschlagen, wieso der funktioniert. War mir nicht bewusst, dass der Typ von &C::member tatsächlich irgendein
    T B::* ist, falls member aus B geerbt wurde.



  • Alles klar, mein Denkfehler wird mir jetzt klar. Ich hatte fälschlicherweise angenommen, dass wie in manch anderer Sprache implizit die Mehrdeutigkeit aufgelöst wird für Operatoren. Mir war zwar bewusst, dass dies für Funktionen gilt, jedoch nicht, dass dies auch für Operatoren gilt. Eine Art die Mehrdeutigkeit aufzulösen, ist die Basisklassen zu ordnen, sodass die erste Klasse bei Mehrdeutigkeit die höchste Priorität hat.


Anmelden zum Antworten