2017-09-25 3 views
5

J'essaie std :: enable_if pour la première fois et j'ai du mal à le faire. Toute recommandation serait appréciée.enable_if avec les constructeurs de copie

Comme exemple de jouet, voici une classe simple vecteur statique, pour lequel je veux définir un constructeur de copie, mais le comportement dépend de la taille relative des vecteurs:

  1. copier uniquement les données dans un plus ou moins de même taille vecteur
  2. copier les données dans un vecteur plus grand, puis pad le reste avec des zéros

Ainsi la classe vecteur est:

template <size_t _Size> 
class Vector 
{ 
    double _data[_Size]; 

public: 
    Vector() 
    { 
     std::fill(_data, _data + _Size, 0.0); 
    } 

    const double* data() const 
    { 
     return _data; 
    } 
... 
}; 

Le constructeur de copie devrait soutenir quelque chose comme cela, la copie des 2 premiers éléments de v3 en v2:

Vector<3> v3; 
Vector<2> v2(v3); 

J'ai essayé un constructeur de copie pour le comportement 1. comme celui-ci, qui compile:

template <size_t _OtherSize, 
    typename = typename std::enable_if_t<_Size <= _OtherSize>> 
    Vector(const Vector<_OtherSize>& v) : Vector() 
    { 
     std::copy(v.data(), v.data() + _Size, _data); 
    } 

mais le compilateur ne peut pas le distinguer du comportement 2. même si les conditions enable_if s'excluent mutuellement.

template <size_t _OtherSize, 
    typename = typename std::enable_if_t<_OtherSize < _Size>> 
    Vector(const Vector<_OtherSize>& v) : Vector() 
    { 
     std::copy(v.data(), v.data() + _OtherSize, _data); 
     std::fill(_data + _OtherSize, _data + _Size, 0.0); 
    } 

J'ai aussi essayé de mettre enable_if dans l'argument à la place, mais il ne pouvait pas en déduire la valeur de _OtherSize:

template <size_t _OtherSize> 
    Vector(const typename std::enable_if_t<_Size <= _OtherSize, 
    Vector<_OtherSize>> & v) 
    : Vector() 
    { 
     std::copy(v.data(), v.data() + _Size, _data); 
    } 

Quelle est la meilleure façon de le faire (en utilisant enable_if, et non d'un simple si déclaration)?

Merci

+4

Un constructeur de copie ne peut pas être un modèle, par définition. Vous pouvez avoir un constructeur basé sur un modèle qui le copie, mais il ne sera toujours pas un constructeur de copie. ;]] – ildjarn

+1

Ce n'est pas le problème, mais les noms qui commencent par un trait de soulignement suivi d'une lettre majuscule ('_Size',' _OtherSize') et les noms qui contiennent deux traits de soulignement consécutifs sont réservés à l'implémentation. Ne les utilisez pas dans votre code. –

+1

Indépendant mais vous n'avez pas besoin de 'typename' avant' std :: enable_if_t'. – Oktalist

Répondre

7

Ignorant par défaut, la signature de deux de ces constructeurs est

template <size_t N, typename> 
Vector(const Vector<N>&) 

à savoir, ils sont finalement les mêmes.

Une façon de les différencier est de faire le type de paramètre de modèle dépendant directement à la condition de enable_if:

template <size_t _OtherSize, 
    std::enable_if_t<(_Size <= _OtherSize), int> = 0> 
    Vector(const Vector<_OtherSize>& v) : Vector() 
    { 
     std::copy(v.data(), v.data() + _Size, _data); 
    } 

template <size_t _OtherSize, 
    std::enable_if_t<(_OtherSize < _Size), int> = 0> 
    Vector(const Vector<_OtherSize>& v) : Vector() 
    { 
     std::copy(v.data(), v.data() + _OtherSize, _data); 
     std::fill(_data + _OtherSize, _data + _Size, 0.0); 
    } 

En aparté, des noms comme _Size et _OtherSize sont réservés à la mise en œuvre et donc illégal pour le code d'utilisateur - perdre le trait de soulignement et/ou la lettre majuscule.

Aussi, comme @StoryTeller l'a laissé entendre, vous ne voulez pas que le premier constructeur s'applique quand _OtherSize == _Size, car le constructeur de copie généré par le compilateur a un comportement idéal. Ce constructeur est déjà moins spécialisé que le constructeur de copie pour Vector s de même taille, donc il ne sera pas sélectionné pendant la résolution de surcharge de toute façon, mais il serait préférable de clarifier l'intention en commutant <= à <.

+1

En allant vers votre commentaire, '<=' est problématique, et devrait vraiment être '<'. Si seulement pour éviter la confusion lorsque le "mauvais" c'tor est appelé. – StoryTeller

+2

@StoryTeller: Vous voulez dire que le constructeur ne s'applique pas à la place du copy-c'tor généré par le compilateur? Oui, probablement, mais j'essayais de limiter la réponse aux problèmes linguistiques du code plutôt qu'aux questions logiques (que le PO n'a pas posées). Bon point cependant, je vais en faire une petite note - merci! – ildjarn

+2

Il ne s'appliquera jamais pendant la résolution de surcharge après tout. Il n'y a pas de risque d'ambiguïté, si ce n'est que l'OP se demande pourquoi son point d'arrêt n'est pas touché :) – StoryTeller

4

N'utilisez pas _Cap; ils sont réservés pour la mise en œuvre. En fait, la source std utilise ces noms parce que ils sont réservés.Ne pas imiter les conventions de dénomination interne des en-têtes std/système.

template <size_t O> 
Vector(const Vector<O>& v) : Vector() 
{ 
    constexpr auto to_copy = (std::min)(O, Size); 
    constexpr auto to_fill = Size-to_copy; 
    auto const* src=v.data(); 
    std::copy(src, src + to_copy, _data); 
    std::fill(_data + to_copy, _data + to_copy+to_fill, 0.0); 
} 
Vector(const Vector& v) = default; 

vous trouverez cette optimise jusqu'au code que vous voulez; dans le cas où il n'y a pas de remplissage, std::fill est appelé avec (foo, foo, 0.0), et thr body est une boucle qui est facile à prouver est un noop par un compilateur lorsque le même pointeur est passé deux fois.

+0

Du vous écrivez '(std :: min)' de sorte qu'il est analysé comme une expression et 'std :: min' est considéré comme un id non qualifié? Si oui, pourquoi? – YSC

+3

@YSC: Éviter Windows.h macro shenanigans est une supposition. – ildjarn