Il n'est pas possible de faire de l'auto-déduction du retour d'une fonction.
typename<template R> R fonction() { return 1; }
Il faut utiliser auto
à la place.
auto fonction() { return 1; }
Il faut éviter les const
sur les templates variadiques.
Sinon, cela impose l'utilise de const partout et ainsi, le code ci-dessous devient invalide (même s'il compile quand même) car le compilateur passe le nombre en tant que double
et non const double
.
typename<template ...Args> void f(const Args&&... args){} f(1.2);
J'ai déjà eu des codes plus complexes où cela posait problème.
Le pattern CRTP fait de l'héritage inverse. C'est dans le parent qu'on ajoute les fonctionnalités. Cela a comme avantage de s'affranchir des méthode virtuelles et le gain de performance résulte dans le fait que les méthodes sont optimisées par le compilateur via les fonctions inline.
Principe du CRTP : on définit l'appel des fonctions dans la classe de base (qui est template
). On accède à la classe enfant via un cast
du template
. La classe dérivée hérite de la classe de base avec elle même comme template
.
template <typename T> class CRTPBase { private: T& impl() { return *static_cast<T*>(this); } }; class CRTPDerived : public CRTPBase<CRTPDerived> { };
class VirtualBase { public : virtual int tick(int n) = 0; }; class VirtualDerived : public VirtualBase { public : int m_counter; public : VirtualDerived() : m_counter(0) {} int tick(int n) { m_counter += n; return m_counter; } }; int test_virtual_methods (int test_run) { VirtualBase* pObj = static_cast<VirtualBase*>(new VirtualDerived); for( int i = 0 ; i < test_run; i++ ) { for( int j = 0 ; j < test_run; j++ ) { pObj->tick(j); } } return static_cast<VirtualDerived*>(pObj)->m_counter; } int main (int argc, char** argv) { return test_virtual_methods(2000); }
template <typename T> class CRTPBase { public: int tick(int n) { return impl().tick(n); } private: T& impl() { return *static_cast<T*>(this); } }; class CRTPDerived : public CRTPBase<CRTPDerived> { public : int m_counter; public: CRTPDerived() : m_counter(0) {} int tick(int n) { m_counter += n; return m_counter; } }; int test_crtp (int test_run) { CRTPBase<CRTPDerived>* pObj = new CRTPDerived ; for( int i = 0 ; i < test_run; i++ ) { for( int j = 0 ; j < test_run; j++ ) { pObj->tick(j); } } return static_cast<CRTPDerived*>(pObj)->m_counter; } int main (int argc, char** argv) { return test_crtp(2000); }
Le problème majeur du CRTP, il n'y a plus de classe parent commune et il est donc impossible de mettre plusieurs implémentations de CRTPBase
dans un même type de pointeur (std::vector
par exemple).
J'ai essayé de résoudre ce problème en utilisant un std::vector<std::variant<...>>
mais son utilisation avec std::visit
casse les performances.
Performance | -O0 -fno-inline | -O2 -fno-inline | -O2 |
---|---|---|---|
Virtuelle | 1,695s | 0,803s | 0,830s |
CRTP | 3,265s | 1,489s | 0,130s |
Si on tente de faire un héritage multiple, on va avoir une classe parent commune.
clang : error: ambiguous cast from base 'crtp<Sensitivity>' to derived 'Sensitivity'
gcc : error: 'crtp<Sensitivity>' is an ambiguous base of 'Sensitivity'
Visual Studio : error C2594: 'static_cast': ambiguous conversions from 'crtp<T>' to 'T &'
Pour avoir un parent différent, l'astuce est de rajouter un template à crtp qui ne sert à rien.
template<typename T, template<typename> typename> struct crtp { T& underlying() { return static_cast<T&>(*this); } T const& underlying() const { return static_cast<T const&>(*this); } }; template<typename T> struct Scale : public crtp<T, Scale> { void scale(double multiplicator) { this->underlying().setValue(this->underlying().getValue() * multiplicator); } }; template<typename T> struct Square : public crtp<T, Square> { void square() { this->underlying().setValue(this->underlying().getValue() * this->underlying().getValue()); } }; class Sensitivity : public Scale<Sensitivity>, public Square<Sensitivity> { public: double getValue() const { return value_; } void setValue(double value) { value_ = value; } private: double value_ = 0.; }; int main() { Sensitivity s; s.setValue(10.); s.scale(2.); return 0; }
The Curiously Recurring Template Pattern (CRTP) Archive du 12/05/2017 le 13/03/2020
What the Curiously Recurring Template Pattern can bring to your code Archive du 16/05/2017 le 13/03/2020
An Implementation Helper For The Curiously Recurring Template Pattern Archive du 19/07/2017 le 13/03/2020
How to Turn a Hierarchy of Virtual Methods into a CRTP Archive du 22/08/2018 le 13/03/2020
Variadic CRTP: An Opt-in for Class Features, at Compile Time Archive du 22/06/2018 le 13/03/2020
How to Reduce the Code Bloat of a Variadic CRTP Archive du 03/07/2018 le 13/03/2020
Variadic CRTP Packs: From Opt-in Skills to Opt-in Skillsets Archive du 26/06/2018 le 13/03/2020
Removing Duplicates in C++ CRTP Base Classes Archive du 28/08/2018 le 13/03/2020
Ce pattern peut être utilisé par le pattern décorateur.
Le code optimisé généré par le compilateur est parfaitement identique à l'exemple précédent.
class Sensitivity { public: double getValue() const { return value_; } void setValue(double value) { value_ = value; } private: double value_ = 0.; }; template <typename T> struct Scale : public T { void scale(double multiplicator) { this->setValue(this->getValue() * multiplicator); } }; template <typename T> struct Square : public T { void square() { this->setValue(this->getValue() * this->getValue()); } }; class SensitivityEnhanced : public Scale<Square<Sensitivity>> {}; int main() { SensitivityEnhanced s; s.setValue(10.); s.scale(2.); return 0; }
Mixin Classes: The Yang of the CRTP Archive du 12/12/2017 le 10/03/2020
L'utilisation des Mixin classes
est mitigée. Certaines personnes pensent que c'est bien d'ajouter des fonctionnalités par l'héritage. D'autres (dont moi) pensent que si les nouvelles fonctionnalités ne sont pas en rapport avec la classe de base, l'utilisation de la composition est mieux.
Pour Mixin : C++ Mixins - Reuse through inheritance is good... when done the right way Archive du 20/09/2011 le 12/03/2020
Pour Composition : Mixins Are Dead. Long Live Composition Archive du 13/03/2015 le 12/03/2020
Pour mitigé :
On utilisera donc l’héritage quand c’est nécessaire, rappelez vous “un développeur est une personne”. On passera aux mixins quand on est dans la situation “un développeur agit comme un salarié”. On se tournera probablement vers la composition si cette relation s’avère être quelque chose de complexe, un objet nécessitant une classe dédiée.La composition à la rescousse de l'héritage Archive du 18/02/2016 le 12/03/2020
undefined reference to XXX
en oubliant d'instancier un template extern.
C'est possible de ne pas être obligé de rendre inline
toutes les méthodes d'une classe template
. Mais il faut alors explicitement indiquer quelles combinaisons class
/ template
seront utilisées par un template class XX<YY>;
Splitting templated C++ classes into .hpp/.cpp files--is it possible? Archive du 27/08/2019
template<class X> class T { public: void fff(); };
#include "template.h" template<class X> void T<X>::fff() {} // On instancie uniquement T<int> (et donc T<int>::fff()). template class T<int>;
Si on décide de mettre le template class
dans le même fichier que le .cc
, il est indispensable de le mettre tout en bas du fichier, après la définition de toutes les fonctions sinon les fonctions qui peuvent être inline
ne seront pas conservées en symbole.
#include "template.h" int main() { T<int> tint; return tint.fff(); }
class A { template<typename U> void f(); };
template<typename U> void A::f(){} // Instanciation pour U=int template void A::f<int>(); // Spécialisation pour U=double template<> void A::f<double>(){}
template<typename T> class A { template<typename U> void f(U u); };
template<typename T> template<typename U> void A<T>::f(U u){} template void A<int>::f(short u);
error: specialization of XXX after instantiation
error: explicit specialization of XXX after instantiation
error C2908: explicit specialization; XXX has already been instantiated
Il ne faut pas utiliser une classe spécialisée avant qu'elle ne soit définie.
template <typename T> class A {}; // Doit être défini après la spécialisation. A<int> a; template <> class A<int> {};
typeid(T).name();
How to convert typename T to string in c++ Archive du 04/09/2019
type_name.h Archive du 04/09/2019
Is it possible to print a variable's type in standard C++? Archive du 04/09/2019
Comme on veut.
Personnellement, j'utilise toujours typename
.
template<class T> class A{}; template<typename T> class B{}; template<template<typename> class T> class D{}; template<template<class> class T> class F{}; // Ci-dessous seulement à partir de C++17. template<template<typename> typename T> class C{}; template<template<class> typename T> class E{};
Difference of keywords 'typename' and 'class' in templates? Archive du 07/01/2010 le 21/02/2020
Dans tous les exemples ci-dessous, il faut précéder par le code :
#include <iostream> #include <tuple> class A { public: static void f() { std::cout << "A\n"; }; }; class B { public: static void f() { std::cout << "B\n"; }; };
Les types à parcourir sont mis dans le template
.
template <typename Arg, typename... Args> void ff() { Arg::f(); if constexpr (sizeof...(Args) != 0) ff<Args...>(); } int main() { ff<A, B>(); }
Le using
est un std::tuple
avec les types à l'intérieur.
Ici, le std::tuple
est passé comme argument.
template <typename T, typename... Ts> void f(std::tuple<T, Ts...> y) { T::f(); if constexpr (sizeof...(Ts) != 0) f(std::tuple<Ts...>{}); } int main() { using Types = std::tuple<A, B>; f(Types{}); }
template <typename T, typename... Ts> struct AA {}; template <typename T, typename... Ts> struct AA<std::tuple<T, Ts...>> { static void ff() { T::f(); if constexpr (sizeof...(Ts) != 0) AA<std::tuple<Ts...>>::ff(); } }; int main() { using Types = std::tuple<A, B>; AA<Types>::ff(); }
Il est possible de s'affranchir de constexpr
en ajoutant une nouvelle spécialisation de fin de récursion.
template <typename T, typename... Ts> struct AA {}; template <typename T, typename... Ts> struct AA<std::tuple<T, Ts...>> { static void ff() { T::f(); AA<std::tuple<Ts...>>::ff(); } }; template <> struct AA<std::tuple<>> { static void ff() { } };
Je suis obligé de passer par une classe. Je n'arrive pas à le faire avec des fonctions :
template <typename T, typename... Ts> void ff() {}; // gcc : error: non-class, non-variable partial specialization 'ff<std::tuple<_El0, _El ...> >' is not allowed // clang : error: function template partial specialization is not allowed // msvc : error C2995: 'void ff(void)': function template has already been defined template <typename T, typename... Ts> void ff<std::tuple<T, Ts...>>(){}
template <typename... Ts> struct AA2 {}; template <typename... Ts> struct AA2<std::tuple<Ts...>> { static void ff() { (Ts::f(), ...); } }; int main() { using Types = std::tuple<A, B>; AA2<Types>::ff(); }
Compter le nombre de champ d'une classe.
#include <iostream> struct UniversalType { template <typename T> operator T(); // no definition required }; template <typename T, typename... A0> consteval auto MemberCounter(auto... c0) { if constexpr (requires { T{{A0{}}..., {UniversalType{}}, c0...}; }) return MemberCounter<T, A0..., UniversalType>(c0...); else if constexpr ( requires { T{{A0{}}..., {UniversalType{}}, c0...}; } || requires { T{{A0{}}..., c0..., UniversalType{}}; }) return MemberCounter<T, A0...>(c0..., UniversalType{}); return sizeof...(A0) + sizeof...(c0); } int main() { using TestType = struct { int x[3]; float y; char z; }; auto [a, b, c] = TestType{}; // decomposes into 3 std::cout << MemberCounter<TestType>() << std::endl; // prints 3 }
typename
Il faut parfois rajouter le mot clé typename
quand un type est suivi du symbole <
. Cela permet au compilateur de différentier l'opérateur de comparaison <
avec le symbole permettant d'explicité un type dans un template.
error: 'GetB_' does not refer to a value return T::template f<MeyerhofShallowFoundationImpl<U, V>::GetB_>( ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
return T::template f<typename MeyerhofShallowFoundationImpl<U, V>::GetB_>(
template
error: expected ';' after expression retval->impl_ = impl_->f<Action, U...>(std::forward<const Args>(args)...); ^ ;
ou encore
error: expression contains unexpanded parameter pack 'U' retval->impl_ = impl_->f<Action, U...>(std::forward<const Args>(args)...); ^ ~
ou encore
error: expected primary-expression before ')' token return this->deco_->f<123, 456>(); ^
Solution:
retval->impl_ = impl_->template f<Action, U...>(std::forward<const Args>(args)...);
using my_map = type_map< pair<int, float>, pair<char, double>, pair<long, short> >; static_assert(std::is_same_v<my_map::find<int>, float>); static_assert(std::is_same_v<my_map::find<char>, double>); static_assert(std::is_same_v<my_map::find<long>, short>);
template <typename T> struct type_tag { using type = T; }; template <typename K, typename V> struct pair { using first_type = K; using second_type = V; }; template <typename Pair> struct element { static auto value(type_tag<typename Pair::first_type>) -> type_tag<typename Pair::second_type>; }; template <typename... elems> struct type_map : element<elems>... { using element<elems>::value...; template <typename K> using find = typename decltype(type_map::value(type_tag<K>{}))::type; };