629 Stimmen

Vorgefertigte Prüfung auf das Vorhandensein einer Klassenmitgliedfunktion?

Ist es möglich, eine Vorlage zu schreiben, die das Verhalten ändert, je nachdem, ob eine bestimmte Member-Funktion auf eine Klasse definiert ist?

Hier ist ein einfaches Beispiel dafür, was ich schreiben würde:

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 ist, dann wird sie verwendet, ansonsten nicht. Der magische Teil, von dem ich nicht weiß, wie er funktioniert, ist der "FUNCTION_EXISTS"-Teil.

7voto

Michael Burr Punkte 320591

Dies war ein schön kleines Rätsel - tolle Frage!

Hier ist eine Alternative zu Die Lösung von Nicola Bonelli die sich nicht auf den Nicht-Standard typeof Betreiber.

Leider funktioniert es nicht auf GCC (MinGW) 3.4.5 oder Digital Mars 8.42n, aber es funktioniert auf allen Versionen von MSVC (einschließlich VC6) und auf Comeau C++.

Der längere Kommentarblock enthält die Details dazu, wie es funktioniert (oder funktionieren soll). Wie gesagt, ich bin nicht sicher, welches Verhalten standardkonform ist - ich würde Kommentare dazu begrüßen.


Aktualisierung - 7. November 2008:

Es sieht so aus, als ob dieser Code zwar syntaktisch korrekt ist, aber das Verhalten, das MSVC und Comeau C++ zeigen, nicht dem Standard entspricht (Dank an Leon Timmermans y litb für den Hinweis auf die richtige Richtung). Der C++03-Standard besagt Folgendes:

14.6.2 Abhängige Namen [temp.dep]

Absatz 3

In der Definition einer Klassenvorlage oder eines Mitglieds einer Klassenvorlage, wenn ein Basisklasse der Klassenvorlage von einem Template-Parameter abhängt, wird der Basisklassenbereich nicht untersucht bei der Suche nach unqualifizierten Namen entweder zum Zeitpunkt der Definition der Klassenschablone oder des Mitglieds oder während einer Instanziierung der Klassenvorlage oder des Mitgliedes.

Es sieht also so aus, dass, wenn MSVC oder Comeau die toString() Mitgliedsfunktion von T Durchführung der Namenssuche an der Aufrufstelle in doToString() wenn die Vorlage instanziiert wird, ist das nicht korrekt (auch wenn es eigentlich das Verhalten ist, nach dem ich in diesem Fall gesucht habe).

Das Verhalten von GCC und Digital Mars scheint korrekt zu sein - in beiden Fällen ist das Nichtmitglied toString() Funktion ist an den Aufruf gebunden.

Mist - ich dachte, ich hätte eine clevere Lösung gefunden, stattdessen habe ich ein paar Compiler-Fehler entdeckt...


#include <iostream>
#include <string>

struct Hello
{
    std::string toString() {
        return "Hello";
    }
};

struct Generic {};

// the following namespace keeps the toString() method out of
//  most everything - except the other stuff in this
//  compilation unit

namespace {
    std::string toString()
    {
        return "toString not defined";
    }

    template <typename T>
    class optionalToStringImpl : public T
    {
    public:
        std::string doToString() {

            // in theory, the name lookup for this call to 
            //  toString() should find the toString() in 
            //  the base class T if one exists, but if one 
            //  doesn't exist in the base class, it'll 
            //  find the free toString() function in 
            //  the private namespace.
            //
            // This theory works for MSVC (all versions
            //  from VC6 to VC9) and Comeau C++, but
            //  does not work with MinGW 3.4.5 or 
            //  Digital Mars 8.42n
            //
            // I'm honestly not sure what the standard says 
            //  is the correct behavior here - it's sort 
            //  of like ADL (Argument Dependent Lookup - 
            //  also known as Koenig Lookup) but without
            //  arguments (except the implied "this" pointer)

            return toString();
        }
    };
}

template <typename T>
std::string optionalToString(T & obj)
{
    // ugly, hacky cast...
    optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);

    return temp->doToString();
}

int
main(int argc, char *argv[])
{
    Hello helloObj;
    Generic genericObj;

    std::cout << optionalToString( helloObj) << std::endl;
    std::cout << optionalToString( genericObj) << std::endl;
    return 0;
}

7voto

Paul Belanger Punkte 2284

Ein Beispiel, das SFINAE und die partielle Spezialisierung von Vorlagen verwendet, indem eine Has_foo Konzeptprüfung:

#include <type_traits>
struct A{};

struct B{ int foo(int a, int b);};

struct C{void foo(int a, int b);};

struct D{int foo();};

struct E: public B{};

// available in C++17 onwards as part of <type_traits>
template<typename...>
using void_t = void;

template<typename T, typename = void> struct Has_foo: std::false_type{};

template<typename T> 
struct Has_foo<T, void_t<
    std::enable_if_t<
        std::is_same<
            int, 
            decltype(std::declval<T>().foo((int)0, (int)0))
        >::value
    >
>>: std::true_type{};

static_assert(not Has_foo<A>::value, "A does not have a foo");
static_assert(Has_foo<B>::value, "B has a foo");
static_assert(not Has_foo<C>::value, "C has a foo with the wrong return. ");
static_assert(not Has_foo<D>::value, "D has a foo with the wrong arguments. ");
static_assert(Has_foo<E>::value, "E has a foo since it inherits from B");

6voto

nob Punkte 1344

MSVC hat die Schlüsselwörter __if_exists und __if_not_exists ( Doc ). Zusammen mit dem typeof-SFINAE-Ansatz von Nicola könnte ich einen Check für GCC und MSVC erstellen, wie ihn der OP gesucht hat.

Aktualisierung: Die Quelle ist zu finden Hier

5voto

TommyD Punkte 647

Ich weiß, dass diese Frage Jahre alt ist, aber ich denke, dass es für Leute wie mich nützlich wäre, eine vollständigere, aktualisierte Antwort zu haben, die auch für const überladene Methoden wie std::vector<>::begin .

Auf der Grundlage dieser respuesta und dass respuesta auf meine Folgefrage, hier eine ausführlichere Antwort. Beachten Sie, dass dies nur mit C++11 und höher funktionieren wird.

#include <iostream>
#include <vector>

class EmptyClass{};

template <typename T>
class has_begin
{
    private:
    has_begin() = delete;

    struct one { char x[1]; };
    struct two { char x[2]; };

    template <typename C> static one test( decltype(void(std::declval<C &>().begin())) * ) ;
    template <typename C> static two test(...);    

public:
    static constexpr bool value = sizeof(test<T>(0)) == sizeof(one);
};

int main(int argc, char *argv[])
{
    std::cout << std::boolalpha;
    std::cout << "vector<int>::begin() exists: " << has_begin<std::vector<int>>::value << std::endl;
    std::cout << "EmptyClass::begin() exists: " << has_begin<EmptyClass>::value << std::endl;
    return 0;
}

Oder die kürzere Version:

#include <iostream>
#include <vector>

class EmptyClass{};

template <typename T, typename = void>
struct has_begin : std::false_type {};

template <typename T>
struct has_begin<T, decltype(void(std::declval<T &>().begin()))> : std::true_type {};

int main(int argc, char *argv[])
{
    std::cout << std::boolalpha;
    std::cout << "vector<int>::begin() exists: " << has_begin<std::vector<int>>::value << std::endl;
    std::cout << "EmptyClass exists: " << has_begin<EmptyClass>::value << std::endl;
}

Beachten Sie, dass hier ein kompletter Musteraufruf vorgelegt werden muss. Das bedeutet, dass wir, wenn wir auf den resize Existenz der Methode, dann hätten wir die resize(0) .

Tiefe magische Erklärung :

In der ersten veröffentlichten Antwort auf diese Frage wurde test( decltype(&C::helloworld) ) Dies ist jedoch problematisch, wenn die zu prüfende Methode aufgrund von Überladung mehrdeutig ist, so dass der Ersetzungsversuch fehlschlägt.

Um diese Zweideutigkeit zu lösen, verwenden wir eine void-Anweisung, die beliebige Parameter annehmen kann, da sie immer in eine noop und somit ist die Zweideutigkeit aufgehoben und der Aufruf ist gültig, solange die Methode existiert:

has_begin<T, decltype(void(std::declval<T &>().begin()))>

Hier ist die Reihenfolge der Ereignisse: Wir verwenden std::declval<T &>() um einen abrufbaren Wert zu erzeugen, für den begin kann dann aufgerufen werden. Danach wird der Wert von begin als Parameter an eine void-Anweisung übergeben wird. Wir rufen dann den Typ dieses void-Ausdrucks mit dem eingebauten decltype so dass es als Argument für einen Vorlagentyp verwendet werden kann. Wenn begin nicht vorhanden ist, ist die Ersetzung ungültig und gemäß SFINAE wird stattdessen die andere Erklärung verwendet.

5voto

Shubham Punkte 342

Ich habe die Lösung aus dem Dokument https://stackoverflow.com/a/264088/2712152 um sie ein wenig allgemeiner zu gestalten. Da es auch keine der neuen C++11-Features verwendet, können wir es mit alten Compilern verwenden und sollte auch mit msvc funktionieren. Aber die Compiler sollten C99 ermöglichen, dies zu verwenden, da es variadische Makros verwendet.

Das folgende Makro kann verwendet werden, um zu prüfen, ob eine bestimmte Klasse ein bestimmtes Typedef hat oder nicht.

/** 
 * @class      : HAS_TYPEDEF
 * @brief      : This macro will be used to check if a class has a particular
 * typedef or not.
 * @param typedef_name : Name of Typedef
 * @param name  : Name of struct which is going to be run the test for
 * the given particular typedef specified in typedef_name
 */
#define HAS_TYPEDEF(typedef_name, name)                           \
   template <typename T>                                          \
   struct name {                                                  \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U>                                       \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<typename _1::typedef_name>*);    \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

Das folgende Makro kann verwendet werden, um zu prüfen, ob eine bestimmte Klasse eine bestimmte Mitgliedsfunktion mit einer beliebigen Anzahl von Argumenten hat oder nicht.

/** 
 * @class      : HAS_MEM_FUNC
 * @brief      : This macro will be used to check if a class has a particular
 * member function implemented in the public section or not. 
 * @param func : Name of Member Function
 * @param name : Name of struct which is going to be run the test for
 * the given particular member function name specified in func
 * @param return_type: Return type of the member function
 * @param ellipsis(...) : Since this is macro should provide test case for every
 * possible member function we use variadic macros to cover all possibilities
 */
#define HAS_MEM_FUNC(func, name, return_type, ...)                \
   template <typename T>                                          \
   struct name {                                                  \
      typedef return_type (T::*Sign)(__VA_ARGS__);                \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U, U>                                    \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<Sign, &_1::func>*);              \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

Wir können die obigen 2 Makros verwenden, um die Prüfungen für has_typedef und has_mem_func durchzuführen:

class A {
public:
  typedef int check;
  void check_function() {}
};

class B {
public:
  void hello(int a, double b) {}
  void hello() {}
};

HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);

int main() {
  std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
  std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
  std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
  std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
  std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
  std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
  std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
  std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X