Templates et fonctions
Le principe de base
Nous l'avons déjà vu dans la partie sur les fonctions, mais un petit rappel ne fait pas de mal, le principe des template
en C++ est d'écrire un patron de code qui sera utilisé par le compilateur lorsqu'il en aura besoin. L'idée caché derrière les templates est de minimiser le code à écrire pour le développeur. Par exemple, si nous voulons écrire une fonction qui affiche le contenu d'un std::vector
plutôt que de faire N versions de la même fonction nous pouvons utiliser les templates de la manière suivante :
template<typename T>
for( const T& x: v){
std::cout << x << ", ";
}
std::cout << std::endl;
}
Et ensuite nous pouvons utiliser cette fonction pour n'importe quel std::vector<T>
.
std::vector<int> v {1,2,3,4,5};
printVector<int>(v);
Avec du c++ moderne on peut se passer de spécifier le type de T
à l'appel de la fonction. Le compilateur est malin maintenant il le déduit du contexte :
std::vector<int> v {1,2,3,4,5};
Spécialisation de template
Maintenant imaginons que nous voulions afficher en plus du contenu du std::vector<T>
un message spécifiant de quel type est T
. Pour cela nous allons définir une fonction myType
templatée par T
qui va renvoyer une chaine de caractère définissant le type de T
. Il va donc nous falloir faire des versions spécifiques, une pour chaque type. C'est ce qu'on va appeler de la spécialisation de templates. Le principe est de définir la version générique par exemple ici :
template<typename T>
std::string
myType(){
return ""; }
Et ensuite on spécialise la fonction template en spécifiant explicitement son comportement pour certaines valeur du paramètre template T
par exemple :
template<>
std::string myType<int>(){ return "int"; }
template<>
std::string myType<double>(){ return "double"; }
Nous pouvons alors définir la fonction qui affiche le contenu d'un std::vector<T>
de la manière suivante :
template<typename T>
std::cout << "std::vector<" << myType<T>() << ">: ";
for( const T& x: v){
std::cout << x << ", ";
}
std::cout << std::endl;
}
std::vector<int> v {1,2,3,4,5};
std::vector<int>: 1, 2, 3, 4, 5
template<typename T>
if constexpr( std::is_same_v<T, int>){
return "int";
}
else if constexpr(std::is_same_v<T, double>){
return "double";
}
else{
return "";
}
}
Les templates de template
Il y a quand même une limite à la version de printVector
que l'on vient de faire. Considérons par exemple le cas où l'on a un std::vector<std::vector<int>>
:
std::vector<std::vector<int> > v { {1,2,3}, {4,5,6}, {7,8,9}};
Dans ce cas la fonction template que l'on a faite ne va pas bien fonctionner.... Cependant il est possible de faire une spécialisation un peu particulière :
template<typename T, template <typename, typename> class Container>
void printVector(
const std::vector<Container<T, std::allocator<T>>>& v){
for( auto& x: v){
for( const T& y: x){
std::cout << y << ", ";
}
std::cout << std::endl;
}
}
Template de classes
Méthodes template
Nous venons de le voir nous pouvons templater des fonctions. Bon et bien par extension il paraît plutôt évident que l'on peut templater des méthodes de classe, car fondamentalement il s'agit de fonctions. Et bien oui je vous confirme on peut faire ça !
Par exemple :
public:
}
template<typename T>
void info(
const T& data){
std::cout << this->
prefix_ << data << std::endl;
}
protected:
};
p.info(2.34);
p.info("coucou");
[Printer] - 2.34
[Printer] - coucou
public:
Data(
const double&x,
const int& n,
const std::string&
name): x{x}, n{n},
name{
name}{}
template<typename T>
T get(){
if constexpr( std::is_same_v<T, double>){
}
else if constexpr( std::is_same_v<T, int> ){
}
else if constexpr( std::is_same_v<T, std::string>){
}
else{
throw std::invalid_argument("unsupported type");
}
}
protected:
};
Data d(1.27, 42,
"toto");
std::cout << d.get<double>() << std::endl;
std::cout << d.get<int>() << std::endl;
try{
std::cout << d.get<bool>() << std::endl;
} catch( std::invalid_argument& e){
}
Classe template
Généralités
template<typename T>
public:
Number(
const T& x): value_{x}{}
}
}
return ret;
}
template<typename U>
return Number<T>( this->value_ +
static_cast<T
>(other()));
}
protected:
};
std::cout << "a+b = " << (a+b)() << std::endl;
std::cout << "a+c = " << (a+c)() << std::endl;
std::cout << "c+a = " << (c+a)() << std::endl;
Une classe Matrice
Allons plus loin
Quelques utilisations avancées des templates
CRTP
Idée générale
Singleton
template <typename Base>
public:
}
}
protected:
};
template<typename Base>
public:
std::cout << "address(A) = " << this << std::endl;
}
protected:
};
a.print();
b.print();
return EXIT_SUCCESS;
}
Composition
template<typename Format>
public:
void message(
const std::string& msg){
std::cout << this->prefix() << msg << this->suffix() << std::endl;
}
};
protected:
return "\e[1;31m";
}
return "\e[0m";
}
};
protected:
return "\e[3m";
}
return "\e[0m";
}
};
Polymorphisme statique
template<typename Derived>
public:
static_cast<Derived*>(this)->doSomethingSpecial();
}
};
public:
std::cout << "In DerivedA::doSomethingSpecial" << std::endl;
}
};
public:
std::cout << "In DerivedB::doSomethingSpecial" << std::endl;
}
};
Variadic templates