Diese Frage ist alt, aber mit C++11 haben wir eine neue Möglichkeit, die Existenz einer Funktion (oder die Existenz eines beliebigen Nicht-Typ-Members) zu überprüfen, indem wir wieder auf SFINAE zurückgreifen:
template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
-> decltype(os << obj, void())
{
os << obj;
}
template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
-> decltype(obj.stream(os), void())
{
obj.stream(os);
}
template<class T>
auto serialize(std::ostream& os, T const& obj)
-> decltype(serialize_imp(os, obj, 0), void())
{
serialize_imp(os, obj, 0);
}
Nun zu einigen Erklärungen. Erstens: Ich verwende Ausdruck SFINAE zum Ausschluss der serialize(_imp)
Funktionen von der Überladungsauflösung, wenn der erste Ausdruck innerhalb von decltype
nicht gültig ist (d.h. die Funktion existiert nicht).
El void()
wird verwendet, um den Rückgabetyp all dieser Funktionen void
.
El 0
Argument wird verwendet, um die os << obj
Überlast, wenn beide verfügbar sind (wörtliche 0
ist vom Typ int
und daher ist die erste Überladung die bessere Wahl).
Nun wollen Sie wahrscheinlich eine Eigenschaft, die prüft, ob eine Funktion existiert. Glücklicherweise ist es einfach, so etwas zu schreiben. Beachten Sie jedoch, dass Sie einen Trait schreiben müssen selbst für jeden anderen Funktionsnamen, den Sie brauchen könnten.
#include <type_traits>
template<class>
struct sfinae_true : std::true_type{};
namespace detail{
template<class T, class A0>
static auto test_stream(int)
-> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
template<class, class A0>
static auto test_stream(long) -> std::false_type;
} // detail::
template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};
Live-Beispiel.
Und nun zu den Erklärungen. Erstens, sfinae_true
ist ein Hilfstyp und läuft im Grunde auf dasselbe hinaus wie das Schreiben von decltype(void(std::declval<T>().stream(a0)), std::true_type{})
. Der Vorteil ist einfach, dass er kürzer ist.
Als nächstes wird die struct has_stream : decltype(...)
erbt entweder von std::true_type
o std::false_type
je nachdem, ob die decltype
einchecken test_stream
scheitert oder nicht.
Zuletzt, std::declval
gibt Ihnen einen "Wert" des übergebenen Typs, ohne dass Sie wissen müssen, wie Sie ihn konstruieren können. Beachten Sie, dass dies nur innerhalb eines unbewerteten Kontextes möglich ist, wie z.B. decltype
, sizeof
und andere.
Beachten Sie, dass decltype
ist nicht unbedingt erforderlich, da sizeof
(und alle nicht bewerteten Kontexte) haben diese Erweiterung erhalten. Es ist nur so, dass decltype
liefert bereits einen Typ und ist als solcher einfach sauberer. Hier ist ein sizeof
Version einer der Überladungen:
template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
int(*)[sizeof((os << obj),0)] = 0)
{
os << obj;
}
El int
y long
Parameter sind aus demselben Grund noch immer vorhanden. Der Array-Zeiger wird verwendet, um einen Kontext bereitzustellen, in dem sizeof
verwendet werden können.