2010-03-11 8 views
7

J'ai joué avec un ensemble de modèles pour déterminer le type de promotion correct étant donné deux types primitifs en C++. L'idée est que si vous définissez un modèle numérique personnalisé, vous pouvez les utiliser pour déterminer le type de retour, par exemple, de la fonction opérateur + en fonction de la classe transmise aux modèles. Par exemple:Utilisations d'un en-tête de promotion arithmétique C++

// Custom numeric class 
template <class T> 
struct Complex { 
    Complex(T real, T imag) : r(real), i(imag) {} 
    T r, i; 
// Other implementation stuff 
}; 

// Generic arithmetic promotion template 
template <class T, class U> 
struct ArithmeticPromotion { 
    typedef typename X type; // I realize this is incorrect, but the point is it would 
           // figure out what X would be via trait testing, etc 
}; 

// Specialization of arithmetic promotion template 
template <> 
class ArithmeticPromotion<long long, unsigned long> { 
    typedef typename unsigned long long type; 
} 

// Arithmetic promotion template actually being used 
template <class T, class U> 
Complex<typename ArithmeticPromotion<T, U>::type> 
operator+ (Complex<T>& lhs, Complex<U>& rhs) { 
    return Complex<typename ArithmeticPromotion<T, U>::type>(lhs.r + rhs.r, lhs.i + rhs.i); 
} 

Si vous utilisez ces modèles de promotion, vous pouvez plus ou moins traiter vos types définis par l'utilisateur comme si elles sont primitives avec les mêmes règles de promotion étant à leur appliquer. Donc, je suppose que la question que j'ai est serait-ce quelque chose qui pourrait être utile? Et si oui, quelles sortes de tâches communes voulez-vous modéliser pour la facilité d'utilisation? Je pars du principe que le simple fait d'avoir les modèles de promotion serait insuffisant pour une adoption pratique. Incidemment, Boost a quelque chose de similaire dans son en-tête math/tools/promotion, mais c'est vraiment plus pour obtenir des valeurs prêtes à être passées aux fonctions mathématiques standard C (qui attendent 2 ou 2 doubles) et contourner tous les les types intégraux. Est-ce quelque chose d'aussi simple que d'avoir un contrôle total sur la façon dont vos objets sont convertis? TL: DR: Quels types de modèles d'aide trouveriez-vous dans un en-tête de promotion arithmétique au-delà de la machine qui fait la promotion elle-même?

+0

Je suppose qu'à la toute fin vous allez assigner le résultat promu joliment à une variable (de type différent), donc je ne vois pas beaucoup d'utilisation pratique pour cela. Peut-être avec ** auto ** en C++ 0x, mais je pense aussi que ** decltype ** peut prendre en charge la plupart des tâches plus facilement. – UncleBens

+0

Comme il s'avère, cela sera implémenté en C++ 0x comme la structure CommonType, qui exploite decltype pour déterminer le type correct. Si quelqu'un veut un comportement complètement uniforme par rapport aux règles de la promotion/conversion, il devra simplement écrire les variations commutatives des modèles. – BenTrofatter

Répondre

2

Ceci est certainement utile - nous utilisons ce genre de choses dans la bibliothèque mathématique sur laquelle je travaille pour taper correctement les valeurs intermédiaires dans les expressions. Par exemple, vous pourriez avoir un opérateur d'addition templated:

template<typename Atype, typename Btype> 
type_promote<Atype, Btype>::type operator+(Atype A, Btype B); 

De cette façon, vous pouvez écrire un opérateur générique qui gère différents types d'arguments, et il renvoie une valeur du type approprié pour éviter la perte de précision dans la expression dans laquelle il apparaît. Il est également utile (dans des choses comme des sommes vectorielles) pour déclarer correctement les variables internes dans ces opérateurs. En ce qui concerne la question de ce qui doit aller avec ceux-ci: Je viens de vérifier dans notre code source où nous les définissons, et tout ce que nous avons là sont juste la simple déclaration ArithmeticPromotion que vous décrivez - trois versions génériques pour résoudre le complexe -les complexes complexes, réels et complexes, en utilisant les réels spécifiques réels, et ensuite une liste de réels réels - environ 50 lignes de code en tout. Nous n'avons pas d'autres modèles d'aide avec eux, et il ne semble pas (d'après notre utilisation) qu'il existe des modèles naturels que nous utiliserions.

(FWIW, si vous ne voulez pas écrire vous-même, téléchargez notre source de http://www.codesourcery.com/vsiplplusplus/2.2/download.html, et tirer src/vsip/core/promote.hpp. C'est même dans la partie de notre bibliothèque qui est BSD sous licence, mais il ne dit pas si dans le fichier lui-même.)

12

Pour cela, ce que vous pouvez utiliser est l'opérateur ?:. Cela vous donnera le type commun entre deux types. Premièrement, si les deux types sont les mêmes, vous allez bien. Ensuite, si les types diffèrent, vous appelez le ?: et voyez quel type vous récupérez.

Vous devez cas particulier les types non promus char, short et leurs versions signées/non signées depuis appliqué à deux de ces opérandes de types différents, le résultat ne sera ni l'un ni l'autre. Vous devez également prendre en compte le cas où deux classes peuvent être converties en types arithmétiques promus. Pour obtenir ces droits, nous vérifions si le résultat de ?: est un type arithmétique promu (dans l'esprit de la clause 13.6), et utilisez alors ce type.

// typedef eiher to A or B, depending on what integer is passed 
template<int, typename A, typename B> 
struct cond; 

#define CCASE(N, typed) \ 
    template<typename A, typename B> \ 
    struct cond<N, A, B> { \ 
    typedef typed type; \ 
    } 

CCASE(1, A); CCASE(2, B); 
CCASE(3, int); CCASE(4, unsigned int); 
CCASE(5, long); CCASE(6, unsigned long); 
CCASE(7, float); CCASE(8, double); 
CCASE(9, long double); 

#undef CCASE 

// for a better syntax... 
template<typename T> struct identity { typedef T type; }; 

// different type => figure out common type 
template<typename A, typename B> 
struct promote { 
private: 
    static A a; 
    static B b; 

    // in case A or B is a promoted arithmetic type, the template 
    // will make it less preferred than the nontemplates below 
    template<typename T> 
    static identity<char[1]>::type &check(A, T); 
    template<typename T> 
    static identity<char[2]>::type &check(B, T); 

    // "promoted arithmetic types" 
    static identity<char[3]>::type &check(int, int); 
    static identity<char[4]>::type &check(unsigned int, int); 
    static identity<char[5]>::type &check(long, int); 
    static identity<char[6]>::type &check(unsigned long, int); 
    static identity<char[7]>::type &check(float, int); 
    static identity<char[8]>::type &check(double, int); 
    static identity<char[9]>::type &check(long double, int); 

public: 
    typedef typename cond<sizeof check(0 ? a : b, 0), A, B>::type 
    type; 
}; 

// same type => finished 
template<typename A> 
struct promote<A, A> { 
    typedef A type; 
}; 

Si vos types Complex<T> peuvent être convertis en l'autre, ?: ne trouverez pas un type commun. Vous pouvez spécialiser promote pour lui dire comment déterminer un type commun de deux Complex<T>:

template<typename T, typename U> 
struct promote<Complex<T>, Complex<U>> { 
    typedef Complex<typename promote<T, U>::type> type; 
}; 

L'utilisation est simple:

int main() { 
    promote<char, short>::type a; 
    int *p0 = &a; 

    promote<float, double>::type b; 
    double *p1 = &b; 

    promote<char*, string>::type c; 
    string *p2 = &c; 
} 

Notez que pour des utilisations dans le monde réel, vous devriez mieux attraper quelques cas, j'ai laissé de côté pour la simplicité, par exemple <const int, int> devrait être manipulé semblable à <T, T> (vous le meilleur d'abord dépouiller const et volatile et convertir T[N] en T* et T& en T et ensuite déléguer au modèle réel promote - c'est-à-dire boost::remove_cv<boost::decay<T>>::type pour les deux A et B avant de les déléguer). Si vous ne faites pas cela, l'appel à check se retrouvera dans une ambiguïté pour ces cas.