Implementierung von is_callable
-
Hallo,
ich hatte die Idee für eine Implementierung von is_callable hierher: https://stackoverflow.com/questions/15393938/find-out-if-a-c-object-is-callable/15396757#15396757
Diese sollte für Klassen, Funktionen und Lambdas funktionieren.
template<typename T> struct is_callable_impl { private: typedef char(&yes)[1]; typedef char(&no)[2]; struct Fallback { void operator()(); }; struct Derived : T, Fallback { }; template<typename U, U> struct Check; template<typename> static yes test(...); template<typename C> static no test(Check<void (Fallback::*)(), &C::operator()>*); public: static const bool value = sizeof(test<Derived>(0)) == sizeof(yes); }; template<typename T> struct is_callable { typedef typename std::conditional< std::is_function<T>::value, std::true_type, typename std::conditional< std::is_class<T>::value, is_callable_impl<T>, std::false_type >::type >::type type; static const bool value = type::value; };Der Code funktioniert (MSVC 2013, GCC) und tut was er soll. Mein nächster Gedanke war, dass der Standard uns doch die schöne Option std::enable_if gegeben hat.
Nach einigem hin und her, bin ich zu dieser Version gekommen: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.
Als nächsten Schritt wollte ich die beiden 'test'-Funktionen zusammenlegen und den Rückgabetyp bedingt machen mit Hilfe von std::conditional.
template<typename C, class R = typename std::conditional<std::is_same<void(Fallback::*)(), decltype(&C::operator())>::value, no, yes >::type> static R test(...);Der Code funktioniert allerdings auf keiner Plattform.
Da weiß doch bestimmt jemand Rat?

-
Was mir auf die Schnelle auffällt, ist, dass
unions und Lambdas ausgeschlossen werden.unions können denoperator()auch überladen.
-
asfdlol schrieb:
Was mir auf die Schnelle auffällt, ist, dass
unions und Lambdas ausgeschlossen werden.Wie kommste drauf?
-
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.
-
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)
Alsotemplate<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
bools 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.
-
std::is_class<T>::value, // false für unions und unspezifiziert (?) für Lambdasclosure types sind Klassen.
-
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
Tis a literal constant expression, implicitly converted to typeT, 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.
-
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.
-
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.
-
Das kuerzeste ist wahrscheinlich
template<typename C, int = sizeof(&C::operator())> static yes test(int);
-
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...
-
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?
-
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.
-
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.