2010-11-09 5 views
3

En réponse à une autre question quelque part, j'ai écrit ce code.Erreurs utilisant decltype() et SFINAE

struct no_type{}; 
template<typename T> struct has_apply { 
    static decltype(T().apply<0u>(double())) func(T* ptr); 
    static no_type func(...); 
    static const bool result = !std::is_same<no_type, decltype(func(nullptr))>::value; 
}; 

class A { 
public: 
    template< unsigned n > 
    void apply(const double&); 

}; 
class B { 
}; 

int main() 
{ 
    std::cout << std::boolalpha << has_apply<A>::result << '\n'; 
    std::cout << std::boolalpha << has_apply<B>::result << '\n'; 
    std::cin.get(); 
    return(0); 
} 

Maintenant, il me semble à ce résultat devrait être vrai si T offre une fonction membre non statique « appliquer » qui accepte un double rvalue et un paramètre de modèle littéral, et false sinon. Toutefois, l'exemple donné ne parvient pas à compiler pour la classe B lors de la compilation has_apply<B>. Le fait que la substitution de T ait échoué dans l'instruction decltype ne devrait-il pas signifier qu'il appelle simplement l'autre func? N'est-ce pas le genre de SFINAE?

résolus à la mode inutile le plus ridicule jamais:

struct no_type{}; 
template<typename T> struct has_apply { 
    template<typename U> static decltype(U().apply<0u>(double())) func(U*); 
    template<typename U> static no_type func(...); 
    static const bool result = !std::is_same<no_type, decltype(func<T>(nullptr))>::value; 
}; 

class A { 
public: 
    template< unsigned n > 
    void apply(const double&); 

}; 
class B { 
}; 

int main() 
{ 
    std::cout << std::boolalpha << has_apply<A>::result << '\n'; 
    std::cout << std::boolalpha << has_apply<B>::result << '\n'; 
    std::cin.get(); 
    return(0); 
} 

Répondre

3

SFINAE applique en cas d'échec de substitution pour le paramètre de modèle d'un modèle de fonction, et non pour le paramètre de modèle d'un modèle de classe qui a la fonction (non-modèle) dans question en tant que membre, comme c'est le cas dans votre cas.

Après fixation, vous devriez au moins le changement decltype(T().apply<0u>(double()))-decltype(T().template apply<0u>(double())) parce T() expression est d'un type dépendant. La raison de cela est la suivante: lorsque le compilateur voit d'abord T().apply<0u>, il ne sait pas encore rien sur T, alors comment devrait-il analyser les jetons apply et < après .? apply peut être un modèle de membre, puis < démarre la liste d'arguments pour cela. OTOH apply pourrait à la place être un membre non-modèle (par exemple un membre de données), puis < serait analysé en tant qu'opérateur «inférieur à». Il y a une ambiguïté, et il est encore trop tôt pour que le compilateur résolve cela à ce stade. Il y a un besoin pour un mécanisme de désambiguïsation qu'un programmeur pourrait utiliser pour dire au compilateur ce que l'on attend de apply: un modèle ou non. Et voici le .template (ou ->template, ou ::template) construire à la rescousse: s'il est présent, le compilateur sait qu'il devrait être un membre du modèle, sinon s'il n'est pas présent, le compilateur sait que le membre ne devrait pas être un modèle.

Enfin, voici un exemple que j'ai créé qui fonctionne correctement et produit les résultats souhaités sur g ++ 4.5.0 avec -std=c++0x:

#include <iostream> 

template < class T > 
decltype(T().template apply<0u>(double())) f(T &t) 
{ 
    return t.template apply<0u>(5.); 
} 

const char *f(...) 
{ 
    return "no apply<>"; 
} 

class A { 
public: 
    template <unsigned> 
    int apply(double d) 
    { 
     return d + 10.; 
    } 
}; 

class B {}; 

int main() 
{ 
    A a; 
    std::cout << f(a) << std::endl; 
    B b; 
    std::cout << f(b) << std::endl; 
} 

sortie est:

15 
no apply<> 

Maintenant, si vous supprimez les deux .template de la première définition f(), puis la sortie devient:

no apply<> 
no apply<> 

Ce qui est pour indiquer l'échec de substitution pour class A car il n'a aucun membre non-modèle nommé apply. SFINAE en action!

+0

Vous devez également utiliser declval () au lieu de T() dans le cas où T n'a pas de constructeur par défaut. VS2010 ne l'a pas mais il peut être fait: http://stackoverflow.com/questions/2638843/should-i-use-c0x-features-now –

+0

@Noah Roberts: C'est vrai! Cela fait partie des raisons pour lesquelles j'ai dit 'au moins' ci-dessus;) – usta

+0

@usta: Je ne comprends toujours pas. T n'est pas un type dépendant, c'est le type de modèle. Si vous créez une variable membre de type T, vous ne dites pas 'typename T t;', ce que vous faites pour les types dépendants. – Puppy

0

Désolé de poster comme une réponse, mais les commentaires semblent être déroutés pour moi.

J'ai vu des gens commenter declval(), mais ce n'est pas nécessaire. Au lieu de T(), on peut simplement écrire

decltype(*(T*)0->template apply<0u>(double()))) 

qui fonctionne, car il apparaît seulement à l'intérieur decltype et n'est pas évalué lors de l'exécution.Une autre option serait de déclarer

T T_obj(void); 

puis utilisez

decltype(T_obj().template apply<0u>(double()))) 

En aparté, je me souviens avoir lu que Stroustrup a conçu la langue parce qu'il ne voulait pas faire un travail avec les mauvais outils. Le C++ n'est-il pas la mauvaise langue pour la métaprogrammation?

Alors que C++ 0x améliore un peu les choses, cela ne semble pas être l'objectif. Existe-t-il un autre langage comme "proche du métal" comme C++ qui fournit de meilleurs outils pour écrire du métacode qui va générer du code lors de la compilation?

+0

Le meilleur que vous pouvez obtenir en dehors de C++ est peut-être D. – Puppy

+1

'declval' n'est pas strictement nécessaire comme vous le signalez correctement, et les gens se sont débrouillés sans ça depuis de nombreuses années maintenant. Mais alors 'std :: move' n'est pas non plus strictement nécessaire, car' static_cast' -ing au type de référence rvalue approprié aurait le même effet. Ils sont juste des moyens pratiques d'exprimer des choses. Et la commodité importe si vous me demandez. Il est possible qu'après quelques années les gens réagissent avec "Huh?!" lorsque vous voyez quelque chose comme '* (T *) 0', parce que' declval' est plus agréable. – usta

+0

Et vous auriez dû écrire '((T *) 0) -> template' ou' (* (T *) 0) .template' au lieu de '* (T *) 0-> template'. Un autre point en faveur de 'declval' :). – usta

Questions connexes