5

J'examine le livre de Stroustroup "Programmation C++ 4ème édition". Et j'essaie de suivre son exemple sur la conception de la matrice. Sa classe matricielle dépend fortement des modèles et je fais de mon mieux pour les comprendre. Voici l'une des classes d'aide pour cette matriceJ'ai besoin d'aide pour comprendre la fonction du template avec les paramètres de type typename complexes

A Matrix_slice est la partie de la mise en œuvre de la matrice qui associe un ensemble de à l'emplacement des indices d'un élément. Il utilise l'idée de tranches généralisées (§40.5.6):

template<size_t N> 
struct Matrix_slice { 
    Matrix_slice() = default; // an empty matrix: no elements 
    Matrix_slice(size_t s, initializer_list<size_t> exts); // extents 
    Matrix_slice(size_t s, initializer_list<size_t> exts, initializer_list<siz e_t> strs);// extents and strides 
    template<typename... Dims> // N extents 
    Matrix_slice(Dims... dims); 


    template<typename... Dims, 
    typename = Enable_if<All(Convertible<Dims,size_t>()...)>> 
    size_t operator()(Dims... dims) const; // calculate index from a set of subscripts 

    size_t size; // total number of elements 
    size_t start; // star ting offset 
    array<size_t,N> extents; // number of elements in each dimension 
    array<size_t,N> strides; // offsets between elements in each dimension 
}; 
I 

Voici les lignes qui construisent le sujet de ma question:

template<typename... Dims, 
     typename = Enable_if<All(Convertible<Dims,size_t>()...)>> 
     size_t operator()(Dims... dims) const; // calculate index from a set of subscripts 

plus tôt dans le livre il décrit comment Enable_if et All() sont mis en œuvre:

template<bool B,typename T> 
using Enable_if = typename std::enable_if<B, T>::type; 


constexpr bool All(){ 
    return true; 
} 
template<typename...Args> 
constexpr bool All(bool b, Args... args) 
{ 
    return b && All(args...); 
} 

J'ai assez dans formation pour comprendre comment ils fonctionnent déjà et en regardant sa mise en œuvre Enable_if je peux en déduire la fonction convertible ainsi:

template<typename From,typename To> 
bool Convertible(){ 
    //I think that it looks like that, but I haven't found 
    //this one in the book, so I might be wrong 
    return std::is_convertible<From, To>::value; 
} 

Alors, je peux undersand les éléments constitutifs de cette déclaration de fonction de modèle mais je suis confus en essayant pour comprendre comment ils travaillent altogather. J'espère que vous pourriez aider à

template<typename... Dims, 
//so here we accept the fact that we can have multiple arguments like (1,2,3,4) 

     typename = Enable_if<All(Convertible<Dims,size_t>()...)>> 
     //Evaluating and expanding from inside out my guess will be 
     //for example if Dims = 1,2,3,4,5 
     //Convertible<Dims,size_t>()... = Convertible<1,2,3,4,5,size_t>() = 
     //= Convertible<typeof(1),size_t>(),Convertible<typeof(2),size_t>(),Convertible<typeof(3),size_t>(),... 
     //= true,true,true,true,true 

     //All() is thus expanded to All(true,true,true,true,true)    
     //=true; 

     //Enable_if<true> 
     //here is point of confusion. Enable_if takes two tamplate arguments, 
     //Enable_if<bool B,typename T> 
     //but here it only takes bool 

     //typename = Enable_if(...) this one is also confusing 

     size_t operator()(Dims... dims) const; // calculate index from a set of subscripts 

Alors qu'avons-nous à la fin? Cette construction

template<typename ...Dims,typename = Enable_if<true>> 
size_t operator()(Dims... dims) const; 

Les questions sont les suivantes:

  1. Ne nous avons besoin le deuxième argument de modèle pour Enable_if
  2. Pourquoi nous avons affectation ('=') pour un typename
  3. Qu'obtenons-nous à la fin?

Mise à jour: Vous pouvez vérifier le code dans le même livre que je fais référence ici The C++ Programming Language 4th edition à la page 841 (matrice Design)

+0

Ceci ne peut pas être un vrai code. 'Enable_if () ...)>' déclenchera l'erreur du compilateur, puisque l'argument template ne peut être qu'une expression connue au moment de la compilation. Une valeur de retour de 'All' ne l'est pas. – SergeyA

+0

@SergeyA Je suppose qu'il manque juste un 'constexpr' devant les définitions de fonctions. Je suis à peu près sûr que ça va fonctionner. – SirGuy

+0

@GuyGreer, OP pourrait. Mais qu'est-ce que l'OP manque d'autre? ;) – SergeyA

Répondre

1

Les constexpr s manquants malgré, std::enable_if est un modèle prend deux paramètres, mais le second est par défaut à void. Il est logique d'écrire un pseudonyme rapide pour conserver cette convention.

D'où l'alias doit être défini comme:

template <bool b, class T = void> 
    using Enable_if = typename std::enable_if<b, T>::type; 

Je n'ai pas aperçu si ce paramètre par défaut est présent dans le livre ou non, juste que cela va résoudre cette question.L'affectation d'un type s'appelle un type alias et fait ce qu'il dit sur l'étain, quand vous vous référez à l'alias, vous faites réellement référence à ce qu'il alias. Dans ce cas, cela signifie que lorsque vous écrivez Enable_if<b> le compilateur élargit cela à typename std::enable_if<b, void>::type pour vous, vous évitant ainsi tout ce type de frappe supplémentaire. Ce que vous obtenez à la fin est une fonction qui est seulement appelable si tous les paramètres que vous lui avez passés sont convertibles en std::size_t. Cela permet d'ignorer les surcharges de fonctions si certaines conditions ne sont pas remplies, ce qui est une technique plus puissante que de simplement faire correspondre les types pour sélectionner la fonction à appeler. Le lien pour std::enable_if a plus d'informations sur pourquoi vous voulez faire cela, mais je préviens les débutants que ce sujet devient un peu gris.

+0

Merci mais encore Il ne répond pas 2 autres questions dans le sujet – Antiusninja

4

Ceci est une base SFINAE. Vous pouvez le lire here, par exemple.

Pour les réponses, j'utilise std::enable_if_t ici au lieu de la EnableIf donnée dans le livre, mais les deux sont identiques:

  1. Comme mentionné dans la réponse par @GuyGreer, le second paramètre de modèle de est défini par défaut sur void.

  2. Le code peut être lu comme une définition de modèle de fonction « normale »

    template<typename ...Dims, typename some_unused_type = enable_if_t<true> > 
    size_t operator()(Dims... dims) const; 
    

    Avec le =, le paramètre est réglé par défaut some_unused_type le type sur le côté droit. Et comme on n'utilise pas explicitement le type some_unused_type, on n'a pas non plus besoin de lui donner un nom et de le laisser simplement vide.

    C'est l'approche habituelle en C++ également trouvée pour les paramètres de fonction. Vérifiez par exemple operator++(int) - on n'écrit pas operator++(int i) ou quelque chose comme ça. Ce qui se passe tous ensemble est SFINAE, qui est l'abréviation de L'échec de substitution n'est pas une erreur. Il y a deux cas ici:

    • En premier lieu, si l'argument booléen de std::enable_if_t est false, on obtient

      template<typename ...Dims, typename = /* not a type */> 
      size_t operator()(Dims ... dims) const; 
      

      Comme il n'y a pas de type valable sur les rhs de typename =, déduction de type échoue. En raison de SFINAE, cependant, cela ne conduit pas à une erreur de compilation mais plutôt à une suppression de la fonction du jeu de surcharge. Le résultat en pratique est comme si la fonction n'aurait pas été définie.

    • En second lieu, si l'argument booléen de std::enable_if_t est true, on obtient

      template<typename ...Dims, typename = void> 
      size_t operator()(Dims... dims) const; 
      

      maintenant typename = void est une définition de type valide et donc il n'y a pas besoin de supprimer la fonction. Il peut donc être normalement utilisé.

Appliqué à votre exemple,

template<typename... Dims, 
     typename = Enable_if<All(Convertible<Dims,size_t>()...)>> 
     size_t operator()(Dims... dims) const; 

les moyens ci-dessus que cette fonction existe seulement si All(Convertible<Dims,size_t>()... est true. Cela signifie essentiellement que les paramètres de la fonction doivent tous être des indices entiers (moi personnellement, j'écrirais cela en termes de std::is_integral<T> cependant).

+0

Un couple de nitpicks (n'hésitez pas à nitpick ma réponse aussi si vous le souhaitez: P), l'échec de déduction de type _peuvent conduire à une erreur de compilation , il ne le fait pas si une autre surcharge de fonction peut être trouvée à sa place. – SirGuy

+0

De plus, 'Convertible' est plus générique que' std :: is_integral' car ce dernier vérifie une occurrence d'une liste prédéterminée de types, alors que le premier accepte les types définis par l'utilisateur avec une conversion 'std :: size_t '(Je suis à peu près sûr qu'une conversion vers n'importe quel type intégral suffira, car après cela une promotion de type est faite plutôt qu'une conversion pour obtenir un' std :: size_t'). – SirGuy

+0

Ceci est une bonne partie de l'explication. Merci beaucoup. – Antiusninja