# Les templates (2/2)

### Template Meta-Programming

Ce terme (TMP) est souvent utilisé pour désigner l'exécution de code à la compilation. Cela peut être vu comme un langage à part entière au sein du C++, où les classes sont des fonctions : leurs paramètres template sont les entrées, et leur contenu forment leur sortie.

```cpp
template <int Tag>
struct tag_t {
    static constexpr int tag = Tag;
};

// Entrées
template <class A, class B>
struct sum_tags {
    // Sorties
    using type = tag_t<A::tag + B::tag>;
};

using arg1   = tag_t<2>;
using arg2   = tag_t<3>;
using f      = sum_tags<arg1, arg2>;
using result = f::type; // tag_t<5>
```

Ce langage possède beaucoup de propriétés des langages fonctionnels, notamment :

* Aucune variable ne peut être modifiée
* On ne dispose pas de boucles (on utilise la récursion)
* Les fonctions sont pures (elles retournent toujours la même chose avec les mêmes arguments)
* Cela implique qu'aucun effet de bord n'est possible
* Les branchements sont fait par *pattern matching* sur les types
* Certaines évaluations sont paresseuses (instanciées uniquement lorsqu'on a besoin d'elles)

Ces propriétés demandent de changer de paradigmes de programmation pour être à l'aise en TMP, et d'être très organisés dans le code pour ne pas se perdre étant donné la verbosité imposée par les templates.

La TMP est parfois comparée au [langage fonctionnel Haskell](https://www.haskell.org/), et la librairie [Boost.Hana](https://www.boost.org/doc/libs/1_67_0/libs/hana/doc/html/index.html#tutorial-introduction) (qui est la librairie de TMP la plus récente et la plus complète) recopie les structures du Haskell et redirige vers la documentation [Typeclassopedia ](https://wiki.haskell.org/Typeclassopedia)(écrite pour le Haskell).

### Substitution Failure Is Not An Error

[Substitution Failure Is Not An Error ](https://en.cppreference.com/w/cpp/language/sfinae)(SFINAE) désigne le fait que des paramètres template déduits incorrects ne déclenchent pas une erreur de compilation, si une autre classe / type / valeur / fonction template correspond aux arguments (trouvée par *pattern matching*). Exemple :

```cpp
// La substitution des types T::type1 et T::type2 peut échouer (si T ne définit
// pas ces types).
template <class T>
typename T::type1 use(T&&) { return {}; }
template <class T>
typename T::type2 use(T&&) { return {}; }

struct Both { using type1 = int; using type2 = float; };
struct T1   { using type1 = char; };

int main() {
    // Erreur : appel ambigu ('use' est déclaré deux fois).
    // use(Both{});
    
    // La seconde fonction 'use' est ignorée : par d'erreurs.
    use(T1{});
    
    // Aucune fonction 'use' valide n'est trouvée.
    // use(int{});
}
```

[`std::void_t`](https://en.cppreference.com/w/cpp/types/void_t) est alors utilisé comme un alias de `void` pour pouvoir spécialiser une classe ou une valeur template, si des arguments template dépendant sont corrects :

{% hint style="info" %}
[`std::declval<T>()`](https://en.cppreference.com/w/cpp/utility/declval) est une fonction qui est utilisée dans des tests de types (souvent avec `decltype`). C'est une fonction qui n'a pas de définition.
{% endhint %}

```cpp
template <class...>
using void_t = void;

template <class T>
std::add_rvalue_reference_t<T> declval() noexcept;

// Dans le cas général, T1 et T2 ne peuvent pas s'additionner.
template <class T1, class T2, class SFINAE = void>
constexpr bool can_be_added = false;

// La spécialisation est choisie par défaut (plus prioritaire).
// Cependant, elle et ignorée si l'expression 'T1 + T2' n'est pas valide.
template <class T1, class T2>
constexpr bool can_be_added<T1, T2, void_t<decltype(
    declval<T1 const&>() + declval<T2 const&>()
)>> = true;

static_assert(can_be_added<float, int>);
static_assert(!can_be_added<void, int>);
```

[`std::enable_if_t`](https://en.cppreference.com/w/cpp/types/enable_if) est un alias d'une classe donnée (`void` par défaut) qui est incorrect si son paramètre booléen template s'évalue à `false`. C'est utilisé pour supprimer des templates selon des conditions connues à la compilation :

```cpp
template <bool Enabled, class T = void>
struct enable_if {};

template <class T>
struct enable_if<true, T> {
    using type = T;
};

// Ce type déduit sera valide uniquement si Enabled est évalué à true.
template <bool Enabled, class T = void>
using enable_if_t = typename enable_if<Enabled, T>::type;


// ici, enable_if_t alias le type de retour (void).

// Sérialisation de types dans le cas général.
// On requiert implicitement 'operator<<(std::ostream&, T const&)'.
template <class T>
enable_if_t<!std::is_trivially_copyable_v<T>>
serialize(std::ostream& os, T const& data) {
    os << data;
}

// Les types TriviallyCopyable peuvent être sérialisés automatiquement :
// on copie leur représentation en mémoire.
template <class T>
enable_if_t<std::is_trivially_copyable_v<T>>
serialize(std::ostream& os, T const& data) {
    auto src = reinterpret_cast<char const*>(&data);
    os.write(src, sizeof(T));
}
```

{% hint style="success" %}
`std::enable_if` peut également contenir une expression incorrecte, auquel cas on le même résultat qu'avec `Enabled == false`.
{% endhint %}

Un pattern est souvent utilisé pour tester la validité d'une expression, et sera implémenté avec `std::is_detected_v` en C++20 : il indique à la compilation si le type template est valide avec les arguments passés.

```cpp
namespace detail {
     // Par défaut, le template général dit que l'expression n'est pas valide.
     template <template <class...> class Expression, class SFINAE, class...Ts>
     constexpr bool is_detected = false;
     
     // Si l'expression arrive à être instantiée avec les paramètres donnés, cette
     // spécialisation est choisie, qui dit alors que l'expression est valide.
     template <template <class...> class Expression, class...Ts>
     constexpr bool is_detected<
          Expression,
          std::void_t<Expression<Ts...>>,
          Ts...
     > = true;
}

// L'utilisation publique du template évite de passer 'void' à la main pour
// correspondre au paramètre testé avec SFINAE.
template <template <class...> class Expression, class...Ts>
constexpr bool is_detected = detail::is_detected<Expression, void, Ts...>;


template <class T1, class T2>
using addition_expression = decltype(
     std::declval<T1 const&>() + std::declval<T2 const&>()
);

static_assert(!is_detected<addition_expression, int, std::vector<int>>);
static_assert( is_detected<addition_expression, int, float>);
```

{% hint style="warning" %}
Le mécanisme SFINAE induit un surcoût lors de la compilation. En cas de ralentissement notable, on préfèrera donc si possible d'autres méthodes (comme le [tag dispatch](https://arne-mertz.de/2016/10/tag-dispatch/)).
{% endhint %}

### Fold expressions

Les *variadic templates* doivent normalement être traités avec de la récursion. Cependant, cela peut demander de la redondance dans le code, et les fonctions imbriquées peuvent gêner le debugging.

Depuis C++17, [les fold expressions](https://en.cppreference.com/w/cpp/language/fold) permettent d'appliquer une récursion de manière automatique, en utilisant un des opérateurs binaires autorisés (il peut s'agir d'une redéfinition d'opérateur).

Dans l'exemple suivant, on utilise `std::index_sequence<Is...>` pour passer les entiers de `0` à `size(tuple) - 1`, afin d'accéder aux éléments du tuple avec `std::get<I>`. `std::make_index_sequence<N>` est un alias pour `std::index_sequence<0, 1, ..., N-1>`.

```cpp
namespace detail {
    // Affiche tous les éléments du tuple passé avec une fold expression.
    template <size_t...Is, class...Ts>
    void print_tuple(
        std::ostream& os,
        std::tuple<Ts...> const& tuple,
        std::index_sequence<Is...>)
    {
        // Fold expression (avec l'opérateur ',').
        (..., (os << ", " << std::get<Is + 1>(tuple)));
    }
}

template <class...Ts>
std::ostream& operator<<(
    std::ostream& os,
    std::tuple<Ts...> const& tuple)
{
    os << "{ ";
    if constexpr (sizeof...(Ts) > 0) {
        os << std::get<0>(tuple);
        // Construit 'std::index_sequence<0, 1, ..., sizeof...(Ts) - 2>'.
        // Utilisé par print_tuple pour déduire les arguments templates.
        constexpr auto seq = std::make_index_sequence<sizeof...(Ts) - 1>{};
        detail::print_tuple(os, tuple, seq);
    }
    return os << " }";
}
```

### Pour aller plus loin

D'autres outils de la librairie standard peuvent être utiles pour simplifier la TMP, comme `std::conditionnal_t<Bool, T1, T2>` qui alias `T1` ou `T2` selon la valeur de `Bool`.

Apprendre un langage fonctionnel comme [le Haskell](https://www.haskell.org/) permet de mieux comprendre certaines librairies et d'améliorer ses compétences en TMP.

[Les expression template](https://en.wikipedia.org/wiki/Expression_templates) permettent d'optimiser des calculs en rendant leur évaluation paresseuse, et en évitant notamment la création d'objets temporaires.

C++20 (ou C++23) apportera la réflexion à la compilation, ce qui permettra d'analyser et de modifier des types durant la compilation. Cela permettra de simplifier beaucoup de tests effectués via SFINAE et apportera de nouvelles fonctionnalités utilisables à la compilation.

### Challenges

(\*\*) Implémenter les booléens`is_iterator<T>` et `is_iterable<T>`. Exemple :

```cpp
static_assert(!is_iterator<int>);
static_assert( is_iterator<int*>);
static_assert(!is_iterable<int*>);
static_assert( is_iterable<std::list<int>>);
```

(\*) Créer la fonction template `nth_times` qui exécute un *function object* N fois. Aucune boucle ne doit être utilisée : `nth_times<200>(f)` va donc copier coller l'appel à f 200 fois.

```cpp
void execute();

int main() {
    nth_times<5>(execute);
    // Equivalent de :
    // execute();
    // execute();
    // execute();
    // execute();
    // execute();
}
```

{% hint style="info" %}
Les compilateurs savent dérouler une boucle for classique quand ses paramètres sont connus à la compilation. `nth_times` sert donc ici uniquement d'exercice.
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://cours-cpp.gitbook.io/resources/abstractions/les-templates-2-2.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
