2017-08-23 3 views
4

Ich habe ein Problem mit einer C++ - Meta-Funktion, die ich nicht verstehe. Ich kompiliere auf Apples Build von clang 8.1.0 mit C++ 14. Arbeitscode, der das Problem veranschaulicht, ist unten.Probleme mit einer C++ - Metafunktion, um festzustellen, ob eine Funktion existiert

Ich habe eine Metafunktion von anderswo abgeschrieben und ich versuche es zu benutzen. Es soll Funktionen mit dem Namen 'bananify' erkennen, die einen Parameter des Typs haben, der an die Metafunktion übergeben wird. Sie nennen es als ...

BananifyDetector<int>::value 

ist, sollte wahr zurück, wenn es eine deklarierte Funktion der Form sehen ...

bananify(int) 

Das Problem ist, dass es funktioniert nur, wenn die Funktion gesucht wird für ist vor der Vorlage Definition BananifyFinder, im Gegensatz zu der Instanziierung davon deklariert. Also in meinem Beispiel-Code würde ich beide erwarten,

BananifyFinder<int> 
BananifyFinder<std::string> 

zu haben mit dem Code ist es mir gelungen unten, aber da, wo bananify (std :: string) definiert wurde, es funktioniert nicht.

Dies ist frustrierend, als ob ich Funktionsmelder in Header-Dateien, die ich Reihenfolge im Client-Code berücksichtigen muss, was eine tiefe Schmerzen und möglicherweise unmöglich ist, unter bestimmten Umständen richtig zu bringen.

Ich bin mir nicht sicher, was hier vor sich geht. Ist es ein C++ - Feature, ein Bug in Clang oder etwas Dummes, das ich gemacht habe?

Jede Hilfe wird geschätzt.

#include <iostream> 
#include <type_traits> 
//////////////////////////////////////////////////////////////////////////////// 
// A bananify function to be detected 
// This is successfully found. 
double bananify(int) 
{ 
    return 0.0; 
} 

/// A meta function that detects if a single argument function named 'bananify' 
/// exists with takes an argument of the type passed to the metafunction. 
/// 
/// Note, automatic casts will get in the way occasionally so if function 
/// bananify(float) exists, a BananifyFinder<int>::value will return true. 
template<class ARG1> 
class BananifyFinder { 
private : 
    template<typename ...> using VoidT_ = void; 

    template<typename A1, typename = void> 
    struct Test_ : std::false_type 
    { 
    typedef void returnType; 
    }; 

    template<typename A1> 
    struct Test_<A1, VoidT_<decltype(bananify(std::declval<A1>()))>> : std::true_type 
    { 
    typedef decltype(bananify(std::declval<A1>())) returnType; 
    }; 

public : 
    typedef typename Test_<ARG1>::returnType returnType; 
    constexpr static bool value = Test_<ARG1>::value; 
}; 

//////////////////////////////////////////////////////////////////////////////// 
// A bananify function to be detected that takes std::strings 
// This fails to be found, but if we move it before the declaration of BananifyFinder it 
// will be found; 
std::string bananify(std::string) 
{ 
    return "s"; 
} 

// dummy class with no bananify function to be found 
class Nothing{}; 

// check the results of the metafunction 'T' 
template<class T> 
void CheckBanana(const std::string &str) 
{ 
    using DetectedType = BananifyFinder<T>; 
    std::cout << str << " detected is " << DetectedType::value << std::endl; 
    std::cout << str << " returns is " << typeid(typename DetectedType::returnType).name() << std::endl << std::endl; 
} 

//////////////////////////////////////////////////////////////////////////////// 
int main(int argc, char *argv[]) 
{ 
    // this should print "BananifyFinder<int> 1 d" 
    CheckBanana<int>("BananifyFinder<int> "); 

    // this should print "BananifyFinder<std::string> 1 NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE" 
    // but it prints "BananifyFinder<std::string> 0 v" 
    // FAILS 
    CheckBanana<std::string>("BananifyFinder<std::string> "); 

    // this should print "BananifyFinder<Nothing> 0 v" 
    CheckBanana<Nothing>("BananifyFinder<Nothing> "); 
} 
+1

korrekt zu sein, 'banify (std :: string)' muss detektiert werden _declared_ (und nicht _defined_), bevor die Vorlage 'BananifyFiner' werden . – YSC

+0

Wären Sie in der Lage, einen 'std :: vector' zu verwenden, bevor Sie eine' #include ''? Genau das passiert hier. – AndyG

+1

AndyG, es ist eher so, als müsste ich MyClass deklarieren, bevor ich #include, bevor ich es benutzen kann. – brunobignose

Antwort

1

Vorlage wird in zwei Phasen analysiert.

Im ersten Fall werden Ausdrücke, die von den Vorlagenargumenten unabhängig sind, aufgelöst. Im zweiten Fall werden abhängige abhängige Argumente aufgelöst, wobei Argument dependent lookup ausgeführt wird.

decltype(bananify(std::declval<A1>()))> ist argumentabhängige Konstruktion (abhängig von A1).

Von this page

Und ADL prüft Funktionsdeklarationen mit externer Bindung, die von beiden Kontexten der Template-Definition sichtbar sind und die Vorlage Instanziierung Kontext (mit anderen Worten, eine neue Funktion Erklärung nach Template-Definition hinzugefügt macht es nicht sichtbar, außer über ADL).

So Ihr Code aussehen in std:: (mit ADL) und findet keine bananify Funktion.

Das Verschieben vor der Template-Instanziierung reicht aus, um es für die Suche zu qualifizieren.

+0

Thanks finden wird! Du hast eine Bearbeitung vorgenommen und deine Antwort wurde erneut angezeigt. – brunobignose

+0

Ich habe es "suspendiert", während ich es bearbeitet habe, um es zu verdeutlichen. – Davidbrcz

1

Ich glaube, dass die bananify Referenz in der Vorlage vor Instanziierung aufgelöst wird, da es nicht abhängig ist. Daher werden die nicht deklarierten Überschreibungen nicht gesehen.

Typischerweise wollen Sie für Funktionen als Mitglieder einer Art zur Verfügung stehen suchen, anstatt auf der obersten Ebene, wobei in diesem Fall das Problem verschwindet:

#include <iostream> 
#include <type_traits> 
#include <typeinfo> 

class A { 
    public: 
    double bananify(int) 
    { 
    return 0.0; 
    } 
}; 

// Find bananify(ARG1) as a member of C: 
template<class C, class ARG1> 
class BananifyFinder { 
private : 
    template<typename ...> using VoidT_ = void; 

    template<typename A1, typename = void> 
    struct Test_ : std::false_type 
    { 
    typedef void returnType; 
    }; 

    template<typename A1> 
    struct Test_<A1, VoidT_<decltype(std::declval<C>().bananify(std::declval<A1>()))>> : std::true_type 
    { 
    typedef decltype(std::declval<C>().bananify(std::declval<A1>())) returnType; 
    }; 

public : 
    typedef typename Test_<ARG1>::returnType returnType; 
    constexpr static bool value = Test_<ARG1>::value; 
}; 

class B { 
    public: 
    std::string bananify(std::string) 
    { 
    return "s"; 
    } 
}; 


// check the results of the metafunction 'T' 
template<class C, class T> 
void CheckBanana(const std::string &str) 
{ 
    using DetectedType = BananifyFinder<C,T>; 
    std::cout << str << " detected is " << DetectedType::value << std::endl; 
    std::cout << str << " returns is " << typeid(typename DetectedType::returnType).name() << std::endl << std::endl; 
} 


int main(int argc, char *argv[]) 
{ 
    CheckBanana<A,int>("BananifyFinder<int> "); // ok 
    CheckBanana<B,std::string>("BananifyFinder<std::string> "); // ok 
} 
Verwandte Themen