2017-08-23 2 views
4

J'ai un problème avec une méta-fonction C++ que je ne comprends pas. Je compile sur la version d'Apple de clang 8.1.0, en utilisant C++ 14. Le code de travail qui illustre le problème est ci-dessous.Problèmes avec une métafonction C++ pour détecter si une fonction existe

J'ai créé une métafonction d'ailleurs et j'essaie de l'utiliser. Il est destiné à détecter les fonctions nommées 'bananify' qui ont un paramètre du type passé à la métafonction. Vous l'appelez comme ...

BananifyDetector<int>::value 

est vrai devrait revenir si elle peut voir une fonction déclarée de la forme ...

bananify(int) 

Le problème est qu'il ne fonctionne que si la fonction recherchée car est déclaré avant la définition de modèle de BananifyFinder, par opposition à l'instanciation de celui-ci. Donc, dans mon code exemple, je me serais attendu à la fois,

BananifyFinder<int> 
BananifyFinder<std::string> 

avoir réussi avec le code que j'ai ci-dessous, mais à cause de l'endroit où bananify (std :: string) a été définie, il échoue. C'est frustrant comme si je mettais des détecteurs de fonction dans des fichiers d'en-tête que je dois inclure dans le code client, ce qui est une douleur profonde et peut-être impossible à résoudre dans certaines circonstances.

Je ne suis pas sûr de ce qui se passe ici. Est-ce une fonctionnalité C++, un bug dans le clang ou quelque chose de stupide que j'ai fait?

Toute aide appréciée.

#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

Pour être correct, 'banify (std :: string)' devez être _declared_ (et non _defined_) avant le modèle 'BananifyFiner' à détecter . – YSC

+0

Souhaitez-vous être capable d'utiliser un 'std :: vector' avant d'avoir un' #include '? C'est ce qui se passe ici. – AndyG

+1

AndyG, c'est plus comme avoir à déclarer MyClass avant que je #include avant de pouvoir l'utiliser. – brunobignose

Répondre

1

Les modèles sont analysés en deux phases.

Dans le premier, les expressions indépendantes des arguments du modèle sont résolues. Dans la seconde, les dépendances d'argument dépendant du modèle sont résolues où Argument dependent lookup est exécuté.

decltype(bananify(std::declval<A1>()))> est une construction dépendant de l'argument (dépend de A1).

De this page

et ADL examine les déclarations de fonctions avec une liaison externe qui sont visibles à la fois le contexte de la définition du modèle et le modèle contexte instanciation (autrement dit, en ajoutant une nouvelle déclaration fonction après la définition du modèle ne le rend pas visible, sauf via ADL).

Ainsi, votre code en regard std:: (avec ADL) et ne trouve pas une fonction bananify.

Le déplacer avant l'instanciation du modèle est suffisant pour le qualifier pour la recherche.

+0

Merci! Vous avez fait un montage et vous avez réapparu. – brunobignose

+0

Je l'ai "suspendu" pendant que je l'éditais pour le clarifier. – Davidbrcz

1

Je crois que la référence bananify est résolue dans le modèle avant l'instanciation, car elle n'est pas dépendante. Par conséquent, les remplacements non déclarés ne sont pas visibles.

En général, vous voulez rechercher des fonctions étant disponibles en tant que membres d'un type, plutôt qu'en haut niveau, auquel cas le problème disparaît:

#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 
}