
Andy
Ist es möglich, eine Vorlage zu schreiben, die das Verhalten ändert, je nachdem, ob eine bestimmte Elementfunktion für eine Klasse definiert ist?
Hier ist ein einfaches Beispiel dafür, was ich schreiben möchte:
template<class T>
std::string optionalToString(T* obj)
{
if (FUNCTION_EXISTS(T->toString))
return obj->toString();
else
return "toString not defined";
}
Also, wenn class T
hat toString()
definiert, dann verwendet es es; ansonsten nicht. Der magische Teil, den ich nicht kenne, ist der Teil “FUNCTION_EXISTS”.

Xeo
Diese Frage ist alt, aber mit C ++ 11 haben wir eine neue Möglichkeit, auf das Vorhandensein einer Funktion (oder wirklich eines Nicht-Typ-Members) zu prüfen, indem wir uns wieder auf SFINAE verlassen:
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. Als erstes verwende ich Ausdruck SFINAE ausschließen serialize(_imp)
Funktionen aus der Überladungsauflösung, wenn der erste Ausdruck drin ist decltype
ist nicht gültig (auch bekannt als die Funktion existiert nicht).
Die void()
wird verwendet, um den Rückgabetyp all dieser Funktionen zu erstellen void
.
Die 0
Argument wird verwendet, um die zu bevorzugen os << obj
überladen, wenn beide verfügbar sind (literal 0
ist vom Typ int
und daher passt die erste Überladung besser).
Jetzt möchten Sie wahrscheinlich, dass ein Merkmal überprüft, ob eine Funktion vorhanden ist. Zum Glück ist es einfach, das zu schreiben. Beachten Sie jedoch, dass Sie ein Merkmal schreiben müssen du selbst für jeden anderen gewünschten Funktionsnamen.
#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 weiter zu Erklärungen. Zuerst, sfinae_true
ist ein Helfertyp, und es läuft im Grunde auf dasselbe hinaus wie Schreiben decltype(void(std::declval<T>().stream(a0)), std::true_type{})
. Der Vorteil ist einfach, dass es kürzer ist.
Als nächstes die struct has_stream : decltype(...)
erbt von beiden std::true_type
oder std::false_type
am Ende, je nachdem, ob die decltype
Einchecken test_stream
scheitert oder nicht.
Zuletzt, std::declval
gibt Ihnen einen “Wert” von welchem Typ auch immer Sie übergeben, ohne dass Sie wissen müssen, wie Sie ihn konstruieren können. Beachten Sie, dass dies nur in einem nicht ausgewerteten Kontext möglich ist, z decltype
, sizeof
und andere.
Beachten Sie, dass decltype
ist nicht unbedingt erforderlich, da sizeof
(und alle nicht ausgewerteten Kontexte) haben diese Verbesserung erhalten. Es ist nur so dass decltype
liefert schon 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;
}
Die int
und long
Parameter sind aus demselben Grund immer noch vorhanden. Der Array-Zeiger wird verwendet, um einen Kontext bereitzustellen, wo sizeof
kann verwendet werden.

Morwenn
C++20 – requires
Ausdrücke
Mit C++20 kommen Konzepte und verschiedene Tools wie z requires
Ausdrücke Dies ist eine integrierte Methode, um das Vorhandensein einer Funktion zu überprüfen. Mit ihnen könntest du deine umschreiben optionalToString
funktionieren wie folgt:
template<class T>
std::string optionalToString(T* obj)
{
constexpr bool has_toString = requires(const T& t) {
t.toString();
};
if constexpr (has_toString)
return obj->toString();
else
return "toString not defined";
}
Pre-C++20 – Erkennungs-Toolkit
N4502 schlägt ein Erkennungs-Toolkit zur Aufnahme in die C++17-Standardbibliothek vor, das es schließlich in die Bibliotheksgrundlagen TS v2 geschafft hat. Es wird höchstwahrscheinlich nie in den Standard aufgenommen, weil es von subsumiert wurde requires
Ausdrücke da, aber es löst das Problem immer noch auf etwas elegante Weise. Das Toolkit führt einige Metafunktionen ein, darunter std::is_detected
die verwendet werden kann, um einfach Typ- oder Funktionserkennungs-Metafunktionen darüber zu schreiben. So könnten Sie es verwenden:
template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );
template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;
Beachten Sie, dass das obige Beispiel ungetestet ist. Das Erkennungs-Toolkit ist noch nicht in Standardbibliotheken verfügbar, aber der Vorschlag enthält eine vollständige Implementierung, die Sie einfach kopieren können, wenn Sie sie wirklich brauchen. Es spielt gut mit der C++17-Funktion if constexpr
:
template<class T>
std::string optionalToString(T* obj)
{
if constexpr (has_toString<T>)
return obj->toString();
else
return "toString not defined";
}
C++14 – Boost.Hana
Boost.Hana baut anscheinend auf diesem konkreten Beispiel auf und stellt in seiner Dokumentation eine Lösung für C++14 bereit, also werde ich es direkt zitieren:
[…] Hana bietet eine is_valid
Funktion, die mit generischen C++14-Lambdas kombiniert werden kann, um eine viel sauberere Implementierung derselben Sache zu erhalten:
auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });
Damit haben wir ein Funktionsobjekt has_toString
die zurückgibt, ob der angegebene Ausdruck für das übergebene Argument gültig ist. Das Ergebnis wird als zurückgegeben IntegralConstant
, daher ist constexpr-ness hier kein Problem, da das Ergebnis der Funktion ohnehin als Typ dargestellt wird. Jetzt ist die Absicht nicht nur weniger ausführlich (das ist ein Einzeiler!), sondern auch viel klarer. Weitere Vorteile sind die Tatsache, dass has_toString
kann an Algorithmen höherer Ordnung übergeben und auch im Funktionsbereich definiert werden, sodass der Namespace-Bereich nicht mit Implementierungsdetails verschmutzt werden muss.
Boost.TTI
Ein weiteres etwas idiomatisches Toolkit zur Durchführung einer solchen Prüfung – wenn auch weniger elegant – ist Boost.TTI, eingeführt in Boost 1.54.0. Für Ihr Beispiel müssten Sie das Makro verwenden BOOST_TTI_HAS_MEMBER_FUNCTION
. So könnten Sie es verwenden:
#include <boost/tti/has_member_function.hpp>
// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)
// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;
Dann könnten Sie die verwenden bool
um einen SFINAE-Check zu erstellen.
Erläuterung
Das Makro BOOST_TTI_HAS_MEMBER_FUNCTION
erzeugt die Metafunktion has_member_function_toString
die den überprüften Typ als ersten Vorlagenparameter verwendet. Der zweite Vorlagenparameter entspricht dem Rückgabetyp der Memberfunktion, und die folgenden Parameter entsprechen den Typen der Parameter der Funktion. Das Mitglied value
enthält true
wenn die Klasse T
hat eine Mitgliedsfunktion std::string toString()
.
Alternative, has_member_function_toString
kann einen Member-Funktionszeiger als Vorlagenparameter annehmen. Daher ist ein Austausch möglich has_member_function_toString<T, std::string>::value
durch has_member_function_toString<std::string T::* ()>::value
.
Obwohl diese Frage zwei Jahre alt ist, wage ich es, meine Antwort hinzuzufügen. Hoffentlich wird es die vorherige, unbestreitbar hervorragende Lösung verdeutlichen. Ich habe die sehr hilfreichen Antworten von Nicola Bonelli und Johannes Schaub genommen und sie zu einer Lösung zusammengeführt, die meiner Meinung nach lesbarer, übersichtlicher ist und die nicht erfordert typeof
Verlängerung:
template <class Type>
class TypeHasToString
{
// This type won't compile if the second template parameter isn't of type T,
// so I can put a function pointer type in the first parameter and the function
// itself in the second thus checking that the function has a specific signature.
template <typename T, T> struct TypeCheck;
typedef char Yes;
typedef long No;
// A helper struct to hold the declaration of the function pointer.
// Change it if the function signature changes.
template <typename T> struct ToString
{
typedef void (T::*fptr)();
};
template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
template <typename T> static No HasToString(...);
public:
static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};
Ich habe es mit gcc 4.1.2 überprüft. Der Verdienst geht hauptsächlich an Nicola Bonelli und Johannes Schaub, also geben Sie ihnen eine Stimme, wenn Ihnen meine Antwort hilft 🙂
Eine einfache Lösung für C++11:
template<class T>
auto optionalToString(T* obj)
-> decltype( obj->toString() )
{
return obj->toString();
}
auto optionalToString(...) -> string
{
return "toString not defined";
}
Update, 3 Jahre später: (und das ist ungetestet). Um die Existenz zu testen, denke ich, dass dies funktionieren wird:
template<class T>
constexpr auto test_has_toString_method(T* obj)
-> decltype( obj->toString() , std::true_type{} )
{
return obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
return "toString not defined";
}
10032600cookie-checkTemplate-Check für die Existenz einer Klassenmitgliedsfunktion?yes
Natürlich versteht es sich von selbst, dass die unten stehende(n) Vorlagenantwort(en) nur mit Informationen zur Kompilierzeit funktionieren, dh T muss toString haben. Wenn Sie eine Unterklasse von T that übergeben tut definieren toString, aber T tut es nichtwird Ihnen gesagt, dass toString nicht definiert ist.
– Alice Purcell
2. September 2010 um 9:50 Uhr
Mögliches Duplikat Wie kann überprüft werden, ob ein Elementname (Variable oder Funktion) in einer Klasse vorhanden ist, mit oder ohne Angabe des Typs?, da es ein breiteres Problem mit C++03 bis C++1y abdeckt.
– iammilind
18. März 2016 um 9:13 Uhr
Beachten Sie, dass C++20 dies jetzt mit Konzepten zulässt. Siehe stackoverflow.com/questions/58394556/… und en.cppreference.com/w/cpp/language/constraints
– Etham
2. Dezember 2021 um 21:07 Uhr