DÉCORER AVEC DES UNITÉS Séminaire technique Cours C++(++) 1
Qu’allons nous voir? Première partie Pourquoi? Les autres librairies Units! Seconde partie Quelques notions de métaprogrammation Concept d’unités ScaledUnit PoweredUnit ComposedUnit Vector 2
Pourquoi ajouter des unités? // Calcule la distance, en mètre double CalculerDistance(double depart, double fin) { return fin – depart; } void main() { double start = 10.0; // En m double end = 50.0; // En km double dist = CalculerDistance(start, end); … } 3
Pourquoi ajouter des unités? // Calcule la distance, en mètre double CalculerDistance(double vitesse, double t) { return vitesse * t; } void main() { double start = 10.0; // En mètre double end = 50.0; // En mètre double dist = CalculerDistance(start, end); … } 4
// Calcule la distance, en mètre Metre CalculerDistance(Speed vitesse, Second t) { return vitesse * t; } … void main() { Metre start = 10.0; // En mètre Metre end = 50.0; // En mètre Metre dist = CalculerDistance(start, end); … } Pourquoi ajouter des unités? Erreur de compilation (Le plus clair possible) 5
// Calcule la distance, en mètre Metre CalculerDistance(Metre debut, Metre fin) { return fin - debut; } … void main() { Kilometre start = 10.0; // En kilomètre Kilometre end = 50.0; // En kilomètre Metre dist = CalculerDistance(start, end); … } Pourquoi ajouter des unités? Transformation automatique 6
Les autres librairies Boost::Unit OpenFrameworks Unités de Callum Grant 7
Boost::Units #include … quantity cm(4 * centi * meter); quantity m(4 * meter); quantity r1 = cm + m; cm contient 0.04 r1 contient
Boost::Units quantity cm(4 * centi * meter); quantity m(4 * meter); quantity a(4 * ampere); quantity c(2 * coulomb); quantity n(2 * newton); auto r2 = cm * m * a / (c * n); r2 contient 0.16 m.kg^-1.s quantity ::type, si::system> > r2 9
Boost::Units quantity cm(4 * centi * meter); quantity s(4 * second); auto r3 = cm + s; 22 erreurs de compilation 20 dans xstring et xutility. 10
Boost::Units Une dans boost : La spécialisation du modèle de fonction ‘add_typeof_helper, quantity >::type operator+(const quantity &, quantity &)’ a échoué. L’autre : ‘+’ binaire : ‘quantity ’ ne défini pas cet opérateur ou une conversion vers un type acceptable pour l’opérateur prédéfini er prix pour la clarté!
OpenFramework (Callum Grant) #include meters_by_second speed = cm(4) / s(4); auto result = cm(4) + s(4); // Erreur de compilation! ‘Util::Units::Internals::Convert ::checkConvertible’ utilise une struct de ‘Util::Units::Internals::static_assert2 ’ non défini. Hein??? 12
OpenFramework (Callum Grant) auto result = cm(4) * m(4) * A(4) / (C(2) * N(2)); cout<<result<<endl; 16 cm.m.A.(C.N)^-1 !?!?!?! 0.16 m.s.kg^-1 16 cm.s.kg^-1 13
Units! #include Metre m1 = Metre(4); Centimetre cm1 = Centimetre(4); Centimetre r1 = cm1 + m1; Speed r2 = cm1 / Second(2); auto r3 = cm1 + Second(4); // Erreur de compilation ‘+’ binaire : aucun opérateur trouvé qui accepte un opérande de partie droite de type ‘Second’ (ou il n’existe pas de conversion acceptable) 14
Units! Centimetre cm(4); Metre m(4); Ampere a(4); auto r2 = m * cm * a / (Coulomb(2) * Newton(2)); r2 contient 0.16 m.kg^-1.s Divide ::Type, Kilogram>::Type r2; ComposedUnit, PoweredUnit > r2; 15
Déclaration d’unité ScaledUnit Centimètre, Milliseconde, etc. struct metreBase; typedef standard ::Type Metre; typedef centi ::Type Centimetre; template struct standard { typedef ScaledUnit Type; }; template struct centi { typedef ScaledUnit Type; }; 16
Déclaration d’unité ScaledUnit Centimètre, Milliseconde, etc. struct secondBase; typedef standard ::Type Second; typedef ScaledUnit Minute; typedef ScaledUnit Hour; typedef ScaledUnit Day; 17
Déclaration d’unité PoweredUnit Centimètre carré, Seconde carré, etc. typedef PoweredUnit Centimetre2; typedef PoweredUnit SecondM2; 18
Déclaration d’unité ComposedUnit Newton (Kilogramme mètre par seconde carré), Coulomb, etc. typedef ComposedUnit, SecondM2> Newton; typedef Divide ::Type, Second2>::Type Newton; 19
Déclaration d’une nouvelle unité typedef ScaledUnit PiedDeRoi; typedef ScaledUnit Toise; typedef ScaledUnit PercheDuRoi; 20
Utilisation de la valeur d’une unité Pour faire le lien avec d’autres librairies Pour court-circuiter la validation du compilateur double val = Metre(4).Value(); 21
Où trouver la librairie? 22
NOTIONS DE MÉTAPROGRAMMATION 23
Rappel - Templates template class Foo { bool foo(T val1, T val2) { return val1 == val2; } } void main() { Foo iFoo; Foo dFoo; cout<<iFoo.foo(1,2)<<dFoo.foo(0.0,0.0)<<endl; } 24
Rappel - Templates template class Foo { bool foo(T val1, T val2) { return val1 == val2; } } template<> class Foo { bool foo(double val1, double val2) { return val1 != val2; } 25
Vérifier si deux types sont identiques template struct SameTypes { enum{ Result = 0 }; }; template struct SameTypes { enum{ Result = 1 }; }; 26 SameTypes ::Result
If Else Statique qui retourne des entiers template struct IfElseInt; template struct IfElseInt { enum { Result = T }; }; template struct IfElseInt { enum { Result = F }; }; 27
If Else Statique qui retourne des types template struct IfElseType; template struct IfElseType { typedef T Result; }; template struct IfElseType { typedef F Result; }; 28
CONCEPTS D’UNITÉS 29
Les constructeurs explicit Metre m = 5; Metre m2 = Metre(5); explicit Unit(const double val = 0.0) : m_value(val) { } 30 Erreur de compilation
Opération communes sur des unités Opérations logiques Addition/Soustraction Multiplication par une valeur sans unité Division (par la même unité ou une valeur sans unité) 31
Curiously Recurring Template Pattern Le nom de l’enfant est contenu dans le paramètre générique (template) du parent template class B { … }; class D : public B { … }; 32
Curiously Recurring Template Pattern template class Unit { … }; template<typename U, int N = 1, int D = 1, typename UBase = typename GetBaseUnit ::BaseUnit> class ScaledUnit : public Unit > { }; 33
Curiously Recurring Template Pattern Copy Constructeur Unit(const U& u) : m_value(u.m_value) { } 34
Curiously Recurring Template Pattern Opérations logiques bool operator==(const U& u) const { return m_value == u.m_value; } bool operator!=(const U& u) const { return !(*this == u); } … 35
Curiously Recurring Template Pattern Addition et soustraction U operator-(const U& u) const { return U(m_value - u.m_value); } U operator+(const U& u) const { return U(m_value + u.m_value); } 36
Curiously Recurring Template Pattern Multiplication et division U operator*(double u) const { return U(m_value * u); } Scalar operator/(const U& u) const { return Scalar(m_value / u.m_value); } 37
Trois types pour les unités ScaledUnit Centimètre, Milliseconde, etc. PoweredUnit Centimètre carré, Seconde carré, etc. ComposedUnit Newton (Kilogramme mètre par seconde carré), Coulomb, etc. Scalar! Sans unité 38
La classe ScaledUnit template<typename U, int N = 1, int D = 1, typename UBase = typename GetBaseUnit ::BaseUnit> class ScaledUnit : public Unit > { … }; 39
La classe PoweredUnit template<typename U, int N, int D = 1, typename UBase = typename GetBaseUnit ::BaseUnit> class PoweredUnit : public Unit > { … } 40
template struct GetBaseUnit { typedef U BaseUnit; }; template struct GetBaseUnit > { typedef SUBase BaseUnit; }; template struct GetBaseUnit > { typedef PUBase BaseUnit; }; 41 Utilitaire GetBaseUnit
La classe ComposedUnit template class ComposedUnit : public Unit > { … } 42
Conversion Centimetre cm(Decimetre(4)); 4 dm 40 cm Centimetre : ScaledUnit Decimetre : ScaledUnit (D To * N From ) / (N To * D From ) = Facteur (100 * 1) / (1 * 10) = From To
template struct GetScaleChange { static double Get() { return (static_cast (DT)* static_cast (NF)) / (static_cast (NT)* static_cast (DF)); } }; template struct GetScaleChange { static double Get() { return 1.0; } }; 44 Utilitaire GetScaleChange
Conversion Centimetre2 cm(Decimetre2(4)); 4 dm 2 400 cm 2 Centimetre2 : PoweredUnit Decimetre2 : PoweredUnit ((D To * N From ) / (N To * D From ))^(N Power /D Power ) = Facteur ((100 * 1) / (1 * 10))^(2/1) =
template struct GetChangeFactorBase { static double GetFactor() { return pow(GetScaleChange ::Get(), (N / static_cast (D))); } }; template struct GetChangeFactorBase { static double GetFactor() { return GetScaleChange ::Get(); } }; 46 Utilitaire GetChangeFactorBase
template struct ChangeFactor { … static double GetValue(double value) { return value * GetChangeFactorBase ::Num, GetScale ::Den, GetScale ::Num, GetScale ::Den, GetPower ::Num, GetPower ::Den>::GetFactor(); } }; 47 Utilitaire ChangeFactor
template struct GetScale { static const uint64 Num = 1; static const uint64 Den = 1; }; template struct GetScale > { static const uint64 Num = N * GetScale ::Num; static const uint64 Den = D * GetScale ::Den; }; template struct GetScale > { static const uint64 Num = GetScale ::Num; static const uint64 Den = GetScale ::Den; }; 48 Utilitaire GetScale
template struct GetPower { enum { Num = 1, Den = 1 }; template struct GetPower > { enum { Num = GetPower ::Num * N, Den = GetPower ::Den * D }; 49 Utilitaire GetPower
ScaledUnit 50
Constructeur template ScaledUnit(const ScaledUnit & u) : Unit >( ChangeFactor, ScaledUnit >::GetValue(u.Value()) ) { … } 51
Multiplication et division générale Unité * Unité = Unité 2 Unité / Unité = Sans Unité Unité * Unité N/D = Unité (N/D)+1 Unité * Unité -1 = Sans Unité Unité / Unité N/D = Unité (N/D)-1 Unité * Unité2 = Composition(Unité, Unité2) 52
Les traits template struct OperatorResultType { typedef ScaledUnit MultiplyType; typedef ScaledUnit DivideType; }; 53
Les traits template struct OperatorResultType > { typedef PoweredUnit, 2, 1, UBase> MultiplyType; typedef Scalar DivideType; }; 54
If Else Statique template struct IfElseType; template struct IfElseType { typedef T Result; }; template struct IfElseType { typedef F Result; }; 55
template struct OperatorResultType > { typedef typename IfElseType<PN + PD == 0, Scalar, PoweredUnit, PN + PD, PD, UBase> >::Result MultiplyType; typedef typename IfElseType<PD - PN == 0, Scalar, PoweredUnit, PD - PN, PD, UBase> >::Result DivideType; }; 56 Trait OperatorResultType avec PoweredUnit
Multiplication typename OperatorResultType >::MultiplyType operator*(const ScaledUnit & u) const { return typename OperatorResultType >::MultiplyType(Value() * u.Value()); } 57
Multiplication template typename OperatorResultType >::MultiplyType operator*(const ScaledUnit & u) const { typedef ChangeFactor, ScaledUnit > ChangeFactorType; return typename OperatorResultType >::MultiplyType(Value() * ChangeFactorType::GetValue(u.Value())); } 58
Multiplication template typename TransformUnit, ScaledUnit >::MultiplyType operator*(const ScaledUnit & u) const { typedef TransformUnit, ScaledUnit > TransformType; return TransformType::Multiply(*this, u); } 59
PoweredUnit Comme les ScaledUnit, avec des traits différents 60
Les traits template struct OperatorResultType > { typedef typename IfElseType<N + D == 0, Scalar, PoweredUnit >::Result MultiplyType; }; 61
template struct OperatorResultType >{ typedef typename IfElseType<(N*PD + D*PN) % (D*PD) == 0, typename IfElseType<(N*PD) + (D*PN) == 0, Scalar, typename IfElseType<(N*PD) + (PN*D) == (D*PD), U, PoweredUnit<U, ((N*PD) + (D*PN)) / (D*PD), 1, UBase> >::Result >::Result, PoweredUnit<U, ((N*PD) + (PN*D)) / PGCD ::Value, (D*PD) / PGCD ::Value, UBase> >::Result MultiplyType; }; 62 Trait OperatorResultType avec PoweredUnit
Calcul du PGCD template struct PGCD { enum { Value = PGCD ::Value }; }; template struct PGCD { enum { Value = M }; }; 63
ComposedUnit 64
Particularités On compose par la gauche ComposedUnit, C>, D> Pas besoin de traits comme les autres unités On va utiliser la structure « TransformUnit » Permet de combiner et simplifier des unités Metre.Kilogram.Second -2 == Kilogram.Metre.Second -2 Metre.Kilogram.Second -2 == Kilogram.Centimetre.Second -2 65
Utilisation de TransformUnit template typename TransformUnit, ScaledUnit >::MultiplyType operator*(const ScaledUnit & u) const { typedef TransformUnit, ScaledUnit > TransformType; return TransformType::Multiply(*this, u); } 66
template struct TransformUnit { typedef typename TransformBase ::ReturnTypeMultiply MultiplyType; typedef typename TransformBase ::ReturnTypeDivide DivideType; static MultiplyType Multiply(const U& u, const V& v) { return MultiplyType(u.Value() * (v.Value() * TransformBase ::GetChangeFactorMultiply())); } static DivideType Divide(const U& u, const V& v) { return DivideType(u.Value() / (v.Value() * TransformBase ::GetChangeFactorDivide())); } }; 67 Utilitaire TransformUnit
template struct TransformBase { typedef typename Transform ::ReturnTypeMultiply MultiplyResultType; typedef typename Transform ::ReturnTypeDivide DivideResultType; enum { FindMultiply = Transform ::Find, FindDivide = Transform ::Find }; typedef typename IfElseType<FindMultiply, MultiplyResultType, ComposedUnit >::Result ReturnTypeMultiply; typedef typename IfElseType<FindDivide, DivideResultType, ComposedUnit ::TypeReturn> >::Result ReturnTypeDivide; 68 Utilitaire TransformBase
… static double GetChangeFactorMultiply() { return Transform ::GetChangeFactor(); } static double GetChangeFactorDivide() { return Transform ::GetChangeFactor(); } }; 69 Utilitaire TransformBase
template struct TransformBase > { typedef typename TransformBase ::ReturnTypeMultiply IntermediateMultiplyType; typedef TransformBase MultiplyType; typedef typename MultiplyType::ReturnTypeMultiply ReturnTypeMultiply; enum { FindMultiply = MultiplyType::FindMultiply, … }; static double GetChangeFactorMultiply() { double f = TransformBase ::GetChangeFactorMultiply(); return MultiplyType::GetChangeFactorMultiply() * f; } }; 70 Utilitaire TransformBase avec ComposedUnit
template struct Transform; template struct Transform { typedef V ReturnTypeMultiply; typedef typename InversePower ::TypeReturn ReturnTypeDivide; enum { Find = 1 }; static double GetChangeFactor() { return 1.0; } }; 71 Utilitaire Transform
template struct Transform, V> { typedef typename ScaledUnit ::template OperatorResultType ::MultiplyType MultiplyResultType; enum { Find = SameTypes ::Result || SameTypes ::BaseUnit, UBase>::Result, VIsScalar = SameTypes ::Result }; typedef MultiplyResultType ReturnTypeMultiply; static double GetChangeFactor() { if (Find && !VIsScalar) return ChangeFactor, V>::GetFactor(); return 1.0; } }; 72 Utilitaire Transform
template struct Transform, V> { typedef typename Transform ::MultiplyResultType U1MultiplyType; typedef typename Transform ::MultiplyResultType U2MultiplyType; typedef typename ComposedUnit MultiplyResultTypeNoScalarFound; enum { Find = Transform ::Find || Transform ::Find || SameTypes, V>::Result }; static double GetChangeFactor() { double changeFactorU1 = Transform ::GetChangeFactor(); double changeFactorU2 = Transform ::GetChangeFactor(); return changeFactorU1 * changeFactorU2; } 73 Utilitaire Transform
// If we find a match // If (U1*V==Scalar) ResultType = U2 // elseif (U2*V==Scalar) ResultType = U1 // else ResultType = ComposedUnit // Otherwise, ResultType = ComposedUnit typedef typename IfElseType<SameTypes<Scalar, U1MultiplyType >::Result, U2, MultiplyResultTypeNoScalarFound>::Result FoundMultiplyTypeStep1; typedef typename IfElseType<SameTypes<Scalar, U2MultiplyType>::Result, U1, FoundMultiplyTypeStep1>::Result FoundMultiplyTypeStep2; typedef typename IfElseType<Find, FoundMultiplyTypeStep2, ComposedUnit >::Result ReturnTypeMultiply; typedef ReturnTypeMultiply MultiplyResultType; }; 74 Utilitaire Transform
Particularités Pas besoin de traits comme les autres unités On va utiliser la structure « TransformUnit » Metre.Kilogram.Second -2 == Kilogram.Metre.Second -2 Metre.Kilogram.Second -2 == Kilogram.Centimetre.Second -2 75
ComposedExactlyEqual template struct ComposedExactlyEqual; template struct ComposedExactlyEqual, ComposedUnit > { enum { Equal = ComposedEqual, ComposedUnit >::Find && ComposedEqual, ComposedUnit >::Find }; typedef typename IfElseType<Equal, ComposedUnit, ComposedUnit >::Result Type; }; 76
SFINAE Substitution Failure Is Not An Error Une erreur lors d’une spécialisation de template ne fait pas d’erreur de programmation! Tant qu’il y a au moins une spécialisation qui fonctionne, il n’y a pas d’erreur et la spécialisation est générée. Si ça ne fonctionne pas, aucune génération n’a lieu 77
SFINAE struct Test { typedef int foo; }; template void f(typename T::foo) { … } // Définition #1 template void f(T) { … } // Définition #2 int main() { f (10); // Appel #1. f (10); // Appel #2. Sans erreur (même s’il n’y a // pas de int::foo) grâce à SFINAE } 78
Permettre une méthode selon une condition template struct EnableIf { typedef T Type; }; template struct EnableIf {}; 79 Les structures ne contiennent pas la même interface! SFINAE!
template typename EnableIf, ComposedUnit >::Equivalent, ComposedUnit >::Type& operator=(const ComposedUnit & v) { double f = ComposedExactlyEqual, ComposedUnit >::GetChangeFactor(); Unit >::m_value = v.Value() * f; return *this; } 80 Assignation d’un ComposedUnit
Fonctions utilitaires template typename SqrtTransform ::TypeReturn sqrt(const Unit & u) { return SqrtTransform ::TypeReturn(sqrt(u.Value())); } template typename InversePower ::TypeReturn operator/(const double& d, const Unit & u) { return InversePower ::TypeReturn(d / u.Value()); } 81
template struct SqrtTransform { typedef PoweredUnit<U, 1, 2, typename GetBaseUnit ::BaseUnit> TypeReturn; }; 82 Utilitaire SqrtTransform
template struct SqrtTransform > { enum { RN = IfElseInt ::Result, RD = IfElseInt ::Result }; typedef typename IfElseType<RN == RD, U, PoweredUnit >::Result TypeReturn; }; 83 Utilitaire SqrtTransform
template struct SqrtTransform > { typedef ComposedUnit ::TypeReturn, typename SqrtTransform ::TypeReturn > TypeReturn; }; 84 Utilitaire SqrtTransform
template struct InversePower { typedef PoweredUnit<U, -1, 1, typename GetBaseUnit ::BaseUnit > TypeReturn; }; template<> struct InversePower { typedef Scalar TypeReturn; }; 85 Utilitaire InversePower
template struct InversePower > { typedef PoweredUnit TypeReturn; }; template struct InversePower > { typedef V TypeReturn; }; 86 Utilitaire InversePower
template struct InversePower > { typedef ComposedUnit ::TypeReturn, typename InversePower ::TypeReturn > TypeReturn; }; 87 Utilitaire InversePower
Vector 88
Vector Vecteur mathématique en 3 dimensions Fonctionne avec les unités Vector distance; Second time; Vector vSpeed = distance / time; 89
CONCLUSION Finalement! 90
Conclusion - Unités Ajoute une couche de validation à la compilation Plus besoin de se soucier des conversions Permet une interopérabilité entre différents systèmes (métrique et impérial) 91
Conclusion – C++ et généricité Sky is the limit! Façon différente de penser des algorithmes Se rapproche beaucoup des langages fonctionnels 92
QUESTIONS? 93