2017-01-20 1 views
2

J'ai un petit et joli petit métier qui répond à tous mes besoins jusqu'à maintenant.Résolution d'un constructeur C++ et d'une ambiguïté d'appel

template <typename T> 
class Array 
{ 
    ... 

public: 
    int Data; // custom value 
    virtual void InitData() { Data = 0; } 

    Array(const Array& array); 
    template <typename U, typename = std::enable_if<std::is_same<U, T>::value, U>> Array(const Array<U>& array); 
    template <typename... Ts> Array(const Ts&... items); 

    void Add(const T& item); 
    template <typename... Ts> void Add(const T& item, const Ts&... rest); 
    void Add(const Array& array); 
} 

Le template <typename... Ts> Array(const Ts&... items); me laisse faire Array<T> array = { ... }, et d'attribuer et de retour {...} listes de initialiseur. Parce qu'aucun des constructeurs n'est explicite, c'est incroyablement pratique, mais c'est aussi la raison pour laquelle je suis coincé maintenant.

J'aimerais pouvoir ajouter quelque chose de "raisonnable" aux tableaux. Mon principal cas d'utilisation est en ce moment:

using Curve = Array<float2>; 

class Poly : public Array<float2> { using Array::Array; void InitData() override { Data = 1; } }; 
class Curve2 : public Array<float2> { using Array::Array; void InitData() override { Data = 2; } }; 
class Curve3 : public Array<float2> { using Array::Array; void InitData() override { Data = 3; } }; 

La substance std::is_same<> est au-dessus spécifiquement pour être en mesure de traiter toutes les courbes que le même, mais pas les mêmes: les types de courbes de degré différent, et tout est bien « statiquement tapé ", donc tout ce que je fais dans une fonction comme DrawCurve(const Curve&) est de vérifier le degré et ensuite prendre une mesure appropriée. Curve est un alias sympa pour Array, et Curve2 etc. sont des spécialisations de degré. Cela fonctionne très bien. Quand j'entre dans la construction de courbe, j'ai habituellement un objet de courbe, auquel j'ajoute des points ou des segments de courbe. Donc, je voudrais pouvoir faire:

Curve3 curve; 
curve.Add(float2()); // ambiguity 
curve.Add(Array<float2>()); 

Malheureusement, je reçois une ambiguïté ici quand je l'appelle ajouter, parce que Add() prendra soit un float2 ou un Array<float2>, qui fonctionne très bien, mais un tableau a le constructeur implicite template <typename... Ts> Array(const Ts&...), qui peut prendre float2 comme argument. Ainsi, l'ambiguïté est entre

Array::Add(float2()); // and 
Array::Add(Array<float2>(float2())); 

J'ai essayé de faire des constructeurs qui prennent des tableaux explicites, comme

template <typename A, typename = std::enable_if<std::is_same<A, Array>::value, A>> 
void Add(const Array& array); 

Mais j'obtenir de nouvelles erreurs de conversion de Curve3 à FLOAT2, etc., et il devient un gâchis. Mon espoir est que quelque part dans les profondeurs des modèles ou d'autres goodies C++ réside une solution simple qui est juste ce dont j'ai besoin. (Oui, je sais que je peux juste renommer les méthodes :: AddItem() et :: AddArray() et le problème sera terminé dans une seconde, mais je ne veux pas cela parce que finalement je veux doubler tout cela avec += puis la plupart du temps juste utiliser.

Toutes les idées?

+0

juste branché le code dans [ici] (http://coliru.stacked-crooked.com/a/beaea40305a2119f), il ne semble pas y avoir une ambiguïté entre ces deux ajoute –

Répondre

3

CONSTATENT que vous voulez

template <typename... Ts> Array(const Ts&... items); 

à utiliser uniquement si le bloc de paramètre contient au moins un élément, et le type de cet élément n'est pas une Array instance de template Si le pack de paramètres est vide, ceci devient un constructeur par défaut, alors gérons ce cas séparément. annoncez et définissez explicitement un constructeur par défaut, si nécessaire, et faites-le faire ce qu'il doit faire; maintenant nous pouvons éliminer cette possibilité de ce cas d'utilisation, et aller de l'avant.

Après avoir obtenu que sur le chemin, ce que vous voulez faire ici est d'utiliser ce constructeur que quand il a un argument:

template <typename T1, typename... Ts> Array(const T1 &t1, const Ts&... items); 

Vous devez modifier ce constructeur d'utiliser le t1 explicite, en plus du pack de paramètres existant qu'il utilise. Cela devrait être assez simple, mais cela ne suffira pas. Il y a encore de l'ambiguïté. Vous voulez que ce constructeur soit sélectionné uniquement si T1 n'est pas Array.

Il y a probablement un moyen de trouver quelque chose de compliqué et de le bourrer en un seul std::enable_if, et de le mettre dans ce modèle. Mais pour plus de clarté et de simplicité, j'utiliser une classe d'aide:

template<typename T> class is_not_array : public std::true_type {}; 

template<typename T> 
class is_not_array<Array<T>> : public std::false_type {}; 

Et puis ajoutez un std::enable_if simple dans le modèle de ce constructeur d'utiliser SFINAE pour sélectionner ce constructeur que lorsque le premier paramètre de modèle n'est pas un Array, similaire à la façon dont vous utilisez std::enable_if déjà, dans l'autre constructeur.

Cela devrait résoudre toutes les ambiguïtés. Ce constructeur doit alors être sélectionné uniquement avec au moins un paramètre de modèle qui n'est pas un Array, avec l'assistance de cette classe d'assistance. Quand c'est un Array, ce constructeur ne sera pas résolvable, et ce cas ira à l'autre constructeur.

Je suggère également d'utiliser des références universelles dans les modèles, au lieu de const T & s.

+0

Merci, qui l'a résolu! La métaprogrammation de modèles est encore très mystérieuse pour moi. :) – Alex