=====Restriction=====
====Auto déduction====
Il n'est pas possible de faire de l'auto-déduction du retour d'une fonction.
typename
R fonction()
{
return 1;
}
Il faut utiliser ''auto'' à la place.
auto fonction()
{
return 1;
}
====const Args...====
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
void f(const Args&&... args){}
f(1.2);
J'ai déjà eu des codes plus complexes où cela posait problème.
=====Héritage=====
====CRTP (Curiously recurring template pattern)====
===Principe===
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
class CRTPBase
{
private:
T& impl() { return *static_cast(this); }
};
class CRTPDerived : public CRTPBase
{
};
[[https://github.com/kumarh1982/benchmarks/tree/master/virtual_methods_vs_crtp|Benchmark]] {{ :lang:cpp:class:benchmarks-master-2016-04-26.zip |Archive du 26/04/2016 le 10/03/2020}}
===Virtual sans CRTP===
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(new VirtualDerived);
for( int i = 0 ; i < test_run; i++ )
{
for( int j = 0 ; j < test_run; j++ )
{
pObj->tick(j);
}
}
return static_cast(pObj)->m_counter;
}
int main (int argc, char** argv)
{
return test_virtual_methods(2000);
}
===CRTP sans virtual===
template
class CRTPBase
{
public:
int tick(int n)
{
return impl().tick(n);
}
private:
T& impl() { return *static_cast(this); }
};
class CRTPDerived : public CRTPBase
{
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* 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(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>%%'' mais son utilisation avec ''std::visit'' casse les performances.
===Benchmark===
^ Performance ^ ''-O0 -fno-inline'' ^ ''-O2 -fno-inline'' ^ ''-O2'' ^
| Virtuelle | 1,695s | 0,803s | 0,830s |
| CRTP | 3,265s | 1,489s | 0,130s |
===Héritage multiple===
Si on tente de faire un héritage multiple, on va avoir une classe parent commune.
clang : ''error: ambiguous cast from base 'crtp' to derived 'Sensitivity%%'%%''
gcc : ''error: 'crtp' is an ambiguous base of 'Sensitivity%%'%%''
Visual Studio : ''error C2594: 'static_cast': ambiguous conversions from 'crtp' to 'T &%%'%%''
Pour avoir un parent différent, l'astuce est de rajouter un template à crtp qui ne sert à rien.
template typename>
struct crtp
{
T& underlying()
{
return static_cast(*this);
}
T const& underlying() const
{
return static_cast(*this);
}
};
template
struct Scale : public crtp
{
void scale(double multiplicator)
{
this->underlying().setValue(this->underlying().getValue() * multiplicator);
}
};
template
struct Square : public crtp
{
void square()
{
this->underlying().setValue(this->underlying().getValue() * this->underlying().getValue());
}
};
class Sensitivity : public Scale, public Square
{
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;
}
[[https://www.fluentcpp.com/2017/05/12/curiously-recurring-template-pattern/|The Curiously Recurring Template Pattern (CRTP)]] {{ :lang:cpp:class:the_curiously_recurring_template_pattern_crtp_-_fluent_c_2020-03-10_1_14_10_pm_.html |Archive du 12/05/2017 le 13/03/2020}}
[[https://www.fluentcpp.com/2017/05/16/what-the-crtp-brings-to-code/|What the Curiously Recurring Template Pattern can bring to your code]] {{ :lang:cpp:class:what_the_curiously_recurring_template_pattern_can_bring_to_your_code_-_fluent_c_2020-03-10_1_14_15_pm_.html |Archive du 16/05/2017 le 13/03/2020}}
[[https://www.fluentcpp.com/2017/05/19/crtp-helper/|An Implementation Helper For The Curiously Recurring Template Pattern]] {{ :lang:cpp:class:an_implementation_helper_for_the_curiously_recurring_template_pattern_-_fluent_c_2020-03-10_1_14_21_pm_.html |Archive du 19/07/2017 le 13/03/2020}}
[[https://www.fluentcpp.com/2018/05/22/how-to-transform-a-hierarchy-of-virtual-methods-into-a-crtp/|How to Turn a Hierarchy of Virtual Methods into a CRTP]] {{ :lang:cpp:class:how_to_turn_a_hierarchy_of_virtual_methods_into_a_crtp_-_fluent_c_2020-03-10_1_14_27_pm_.html |Archive du 22/08/2018 le 13/03/2020}}
[[https://www.fluentcpp.com/2018/06/22/variadic-crtp-opt-in-for-class-features-at-compile-time/|Variadic CRTP: An Opt-in for Class Features, at Compile Time]] {{ :lang:cpp:class:variadic_crtp_an_opt-in_for_class_features_at_compile_time_-_fluent_c_2020-03-10_1_14_42_pm_.html |Archive du 22/06/2018 le 13/03/2020}}
[[https://www.fluentcpp.com/2018/07/03/how-to-reduce-the-code-bloat-of-a-variadic-crtp/|How to Reduce the Code Bloat of a Variadic CRTP]] {{ :lang:cpp:class:how_to_reduce_the_code_bloat_of_a_variadic_crtp_-_fluent_c_2020-03-10_1_14_48_pm_.html |Archive du 03/07/2018 le 13/03/2020}}
[[https://www.fluentcpp.com/2018/06/26/variadic-crtp-packs-from-opt-in-skills-to-opt-in-skillsets/|Variadic CRTP Packs: From Opt-in Skills to Opt-in Skillsets]] {{ :lang:cpp:class:variadic_crtp_packs_from_opt-in_skills_to_opt-in_skillsets_-_fluent_c_2020-03-10_1_14_56_pm_.html |Archive du 26/06/2018 le 13/03/2020}}
[[https://www.fluentcpp.com/2018/08/28/removing-duplicates-crtp-base-classes/|Removing Duplicates in C++ CRTP Base Classes]] {{ :lang:cpp:class:removing_duplicates_in_c_crtp_base_classes_-_fluent_c_2020-03-10_1_15_02_pm_.html |Archive du 28/08/2018 le 13/03/2020}}
====Mixin classes (similaire à CRTP mais sans inversion de l'héritage)====
Ce pattern peut être utilisé par le pattern [[helloworld:design_pattern_decorator|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
struct Scale : public T {
void scale(double multiplicator) {
this->setValue(this->getValue() * multiplicator);
}
};
template
struct Square : public T {
void square() { this->setValue(this->getValue() * this->getValue()); }
};
class SensitivityEnhanced : public Scale>
{};
int main() {
SensitivityEnhanced s;
s.setValue(10.);
s.scale(2.);
return 0;
}
[[https://www.fluentcpp.com/2017/12/12/mixin-classes-yang-crtp/|Mixin Classes: The Yang of the CRTP]] {{ :lang:cpp:class:mixin_classes_the_yang_of_the_crtp_-_fluent_c_2020-03-10_1_28_44_pm_.html |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 : [[http://www.thinkbottomup.com.au/site/blog/C%20%20_Mixins_-_Reuse_through_inheritance_is_good|C++ Mixins - Reuse through inheritance is good... when done the right way]] {{ :lang:cpp:template:c_mixins_-_reuse_through_inheritance_is_good..._when_done_the_right_way_think_bottom_up_pty_ltd_2020-03-12_22_27_44_.html |Archive du 20/09/2011 le 12/03/2020}}
Pour Composition : [[https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750|Mixins Are Dead. Long Live Composition]] {{ :lang:cpp:template:mixins_are_dead._long_live_composition_-_dan_abramov_-_medium_2020-03-12_22_58_13_.html |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.[[https://www.synbioz.com/blog/tech/la-composition-a-la-rescousse-de-lheritage|La composition à la rescousse de l'héritage]] {{ :lang:cpp:template:la_composition_a_la_rescousse_de_l_heritage_2020-03-12_23_02_42_.html |Archive du 18/02/2016 le 12/03/2020}}
=====Divers=====
====Avantages/inconvénient du code source dans les fichiers entête====
* Avantages
* Pas besoin d'avoir d'un coté la déclaration des classes (entête) et de l'autre l'implémentation.
* Pour les templates, le compilateur va générer automatiquement le code pour chaque symbole pour chaque variante d'un template. En mettant le code dans l'entête, on est sûr de ne pas avoir des ''undefined reference to XXX'' en oubliant d'instancier un template extern.
* Pas besoin d'utiliser des extern template.
* Inconvénient
* Les fichiers entête sont plus dur à lire pour trouver la liste des méthodes publiques. Sinon, il est possible de déclarer les méthodes dans la classe et de définir les méthodes inline en dessous de la classe.
* Le compilateur va générer le code des fonctions dans chaque fichier source où le template est utilisé.
* Le nombre de fichiers inclus dans les entêtes sera (sauf exception) plus important que si on avait laissé uniquement la déclaration de la classe dans le fichier entête. En effet, certains entrées ne sont peut-être nécessaire que pour l'implémentation et par pour le prototype des méthodes.
* Le lieur va avoir de nombreux fichiers objets ayant les mêmes symboles de type Weak. Il va donc devoir faire beaucoup de nettoyage et le compilateur aura beaucoup travaillé pour rien. Pire, si le code de la méthode template est dépendant d'une macro qui varie entre plusieurs fichiers cpp, seules une des implémentations sera retenue puis généralisée.
====Séparer le code source des fonctions et leur définition dans une classe template====
* Exemple avec une classe template
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;''
[[https://stackoverflow.com/questions/1724036/splitting-templated-c-classes-into-hpp-cpp-files-is-it-possible | Splitting templated C++ classes into .hpp/.cpp files--is it possible?]] {{ :lang:cpp:template:class_-_splitting_templated_c_classes_into_.hpp_.cpp_files--is_it_possible_-_stack_overflow_2019-08-27_08_55_46_.html |Archive du 27/08/2019}}
template
class T
{
public:
void fff();
};
#include "template.h"
template
void T::fff() {}
// On instancie uniquement T (et donc T::fff()).
template class T;
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 tint;
return tint.fff();
}
* Exemple avec une fonction template
class A {
template
void f();
};
template void A::f(){}
// Instanciation pour U=int
template void A::f();
// Spécialisation pour U=double
template<> void A::f(){}
* Exemple avec une fonction template dans une classe template
template
class A {
template
void f(U u);
};
template template void A::f(U u){}
template void A::f(short u);
* Messages d'erreur
''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
class A {};
// Doit être défini après la spécialisation.
A a;
template <>
class A {};
====Afficher en string le type template====
typeid(T).name();
[[https://stackoverflow.com/questions/4484982/how-to-convert-typename-t-to-string-in-c|How to convert typename T to string in c++]] {{ :lang:cpp:template:templates_-_how_to_convert_typename_t_to_string_in_c_-_stack_overflow_2019-09-04_11_16_03_.html |Archive du 04/09/2019}}
[[https://raw.githubusercontent.com/gelldur/common-cpp/master/src/acme/type_name.h|type_name.h]] {{ :lang:cpp:template:type_name.h |Archive du 04/09/2019}}
[[https://stackoverflow.com/questions/81870/is-it-possible-to-print-a-variables-type-in-standard-c/|Is it possible to print a variable's type in standard C++?]] {{ :lang:cpp:template:is_it_possible_to_print_a_variable_s_type_in_standard_c_-_stack_overflow_2019-09-04_11_22_58_.html |Archive du 04/09/2019}}
====class ou typename====
Comme on veut.
Personnellement, j'utilise toujours ''typename''.
* Exemples possibles :
template class A{};
template class B{};
template class T> class D{};
template class T> class F{};
// Ci-dessous seulement à partir de C++17.
template typename T> class C{};
template typename T> class E{};
[[https://stackoverflow.com/questions/2023977/difference-of-keywords-typename-and-class-in-templates|Difference of keywords 'typename' and 'class' in templates?]] {{ :lang:cpp:template:c_-_difference_of_keywords_typename_and_class_in_templates_-_stack_overflow_2020-02-21_20_18_43_.html |Archive du 07/01/2010 le 21/02/2020}}
====Boucles sur des types====
Dans tous les exemples ci-dessous, il faut précéder par le code :
#include
#include
class A {
public:
static void f() { std::cout << "A\n"; };
};
class B {
public:
static void f() { std::cout << "B\n"; };
};
===Fonction template récursive avec les types en dur dans le template===
Les types à parcourir sont mis dans le ''template''.
template
void ff() {
Arg::f();
if constexpr (sizeof...(Args) != 0) ff();
}
int main() {
ff();
}
===Fonction template récursive avec les types dans l'argument via un using = std::tuple===
Le ''using'' est un ''std::tuple'' avec les types à l'intérieur.
Ici, le ''std::tuple'' est passé comme argument.
template
void f(std::tuple y)
{
T::f();
if constexpr (sizeof...(Ts) != 0) f(std::tuple{});
}
int main() {
using Types = std::tuple;
f(Types{});
}
===Fonction template récursive avec les types dans le template (spécialisation) via un using = std::tuple===
template
struct AA {};
template
struct AA> {
static void ff() {
T::f();
if constexpr (sizeof...(Ts) != 0) AA>::ff();
}
};
int main() {
using Types = std::tuple;
AA::ff();
}
Il est possible de s'affranchir de ''constexpr'' en ajoutant une nouvelle spécialisation de fin de récursion.
template
struct AA {};
template
struct AA> {
static void ff() {
T::f();
AA>::ff();
}
};
template <>
struct AA> {
static void ff() {
}
};
Je suis obligé de passer par une classe. Je n'arrive pas à le faire avec des fonctions :
template
void ff() {};
// gcc : error: non-class, non-variable partial specialization 'ff >' 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
void ff>(){}
===Fonction template non récursive avec les types dans le template (spécialisation) via un using = std::tuple===
template
struct AA2 {};
template
struct AA2> {
static void ff() {
(Ts::f(), ...);
}
};
int main() {
using Types = std::tuple;
AA2::ff();
}
====Sérialisation====
Compter le nombre de champ d'une classe.
#include
struct UniversalType {
template
operator T(); // no definition required
};
template
consteval auto MemberCounter(auto... c0) {
if constexpr (requires { T{{A0{}}..., {UniversalType{}}, c0...}; })
return MemberCounter(c0...);
else if constexpr (
requires {
T{{A0{}}..., {UniversalType{}}, c0...};
} ||
requires {
T{{A0{}}..., c0..., UniversalType{}};
})
return MemberCounter(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() << std::endl; // prints 3
}
====Erreurs====
* Il manque un mot clé ''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::GetB_>(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
return T::template f::GetB_>(
* Il manque un mot clé ''template''
error: expected ';' after expression
retval->impl_ = impl_->f(std::forward(args)...);
^
;
ou encore
error: expression contains unexpanded parameter pack 'U'
retval->impl_ = impl_->f(std::forward(args)...);
^ ~
ou encore
error: expected primary-expression before ')' token
return this->deco_->f<123, 456>();
^
Solution:
retval->impl_ = impl_->template f(std::forward(args)...);
===Mapping d'un type vers un autre via une map===
https://stackoverflow.com/questions/68668956/c-how-to-implement-a-compile-time-mapping-from-types-to-types
using my_map = type_map<
pair,
pair,
pair
>;
static_assert(std::is_same_v, float>);
static_assert(std::is_same_v, double>);
static_assert(std::is_same_v, short>);
template
struct type_tag
{
using type = T;
};
template
struct pair
{
using first_type = K;
using second_type = V;
};
template
struct element
{
static auto value(type_tag)
-> type_tag;
};
template
struct type_map : element...
{
using element::value...;
template
using find = typename decltype(type_map::value(type_tag{}))::type;
};