> For the complete documentation index, see [llms.txt](https://cours-cpp.gitbook.io/resources/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cours-cpp.gitbook.io/resources/abstractions/les-lambdas-and-operateurs.md).

# Les lambdas & opérateurs

Le C++ permet de redéfinir des opérateurs comme n'importe quelle fonction : il suffit de respecter le nom de l'opérateur et d'avoir une signature qui lui correspond.

```cpp
struct Pair {
    int data[2];
};

Pair operator+(Pair const& lhs, Pair const& rhs) {
    return { lhs.data[0] + rs.data[0],
             lhs.data[1] + rs.data[1] };
};
```

De nombreuses interfaces implicites utilisées par les fonctions template se basent sur ces opérateurs. par exemple, les itérateurs utilisent au minimum les opérateurs suivants :

* Déréférencer l'itérateur avec `T& operator*()`.
* Accéder aux membres de la valeur de l'itérateur avec `T* operator->()`.
* Incrémenter l'itérateur avec `T& operator++()`.

### Function objects & std::function

Les *function objects* sont les objets pouvant être appelés comme des fonctions. Il s'agit de pointeurs de fonction ou d'objets implémentant l'opérateur `()`. L'opérateur peut être défini plusieurs fois tant que la résolution des appels n'est pas ambigu.

```cpp
// Fonction.
int f(float value) {
    return value - 1;
} 
// Function object.
struct Fun {
    int operator()(float value) {
        return value + 1;
    }
};

// Accepte n'importe quel function object.
// Les arguments et valeurs de retour peuvent être cast implicitement.
template <class F>
int call_with(F&& f, float value) {
    return f(value);
}

int main() {
    // Pointeur de fonction.
    auto ptr = f;
    int i = call_with(ptr, 2.f);
    int j = call_with(Fun{}, 3.14f);
}

```

{% hint style="info" %}
Parfois, ces objets sont appelés *functors* en C++, ce qui n'a rien à voir avec les [functors](https://en.wikipedia.org/wiki/Functor) que l'on retrouve en programmation fonctionnelle.
{% endhint %}

`std::function<Ret(Args...)>` permet de stocker n'importe quel *function object* pouvant être copié, et dont les arguments et la valeur de retour correspondent avec la signature de la classe :

```cpp
// Nos function objects.
struct FortyTwo {
    int operator()() const {
        return 42;
    }
}
struct Counter {
    int value;
    int operator()() {
        return ++value;
    }
};

int main() {
    auto fortytwo = FortyTwo{};
    auto producer = std::function<int()>{ fortytwo  };
    auto counter = Counter{ producer() };
    producer = counter;
    producer();
    producer();
    // Retourne 44.
    return producer();
}
```

L'appel à `std::function` est cependant moins performant qu'un appel direct au *function object*, car il nécessite une indirection qui empêche souvent l'inline, et déclenche une allocation dynamique selon la taille du *function object*.

Le concept de [Callable](https://en.cppreference.com/w/cpp/concept/Callable) permet à (entre autres) `std::function` ou `std::thread` d'utiliser l'objet comme une fonction : cela comprend les *function objects* ainsi que quelques cas supplémentaires, comme les pointeurs de fonction membres. Les *Callables* sont utilisés avec `std:invoke(f, args...)`.

### Lambdas

Parfois appelées closures, les lambdas sont des function objects créées automatiquement par le compilateur. Elles peuvent stocker des variables ou des références, qui sont spécifiées avec la *capture* donnée par les crochets :

```cpp
int i = 1;
int j = 2;

// Le type de la lambda ne peut pas être connu, puiqu'il est créé
// à la compilation. Celle-ci capture i par référence et j par valeur.
auto lambda = [&i, j] (int mul) {
    i += j * mul;
};

// Code équivalent. Par défaut, la lambda est const (elle peut être
// marquée mutable pour enlever le const).
struct lambda_t {
    int& i;
    int j;
    void operator()(float mul) const {
        i += j * mul;
    }
};
auto lambda2 = lambda_t{ i, j };
```

Les captures peuvent également créer de nouvelles variables, et spécifier un mode de capture par défaut : dans ce cas, toutes les variables locales utilisées dans la capture seront capturées par valeur ou par référence. Attention cependant à ne pas avoir de *dangling references* (références désignant un objet n'existant plus) si la lambda vit plus longtemps que les objets capturés par références.

```cpp
int a = 3;
int b = 2;

// Capture a et b par valeur : ils sont copiés dans la lambda.
// Puisqu'elle est marquée mutable, ces copies peuvent être modifiées.
// Le type de retour peut être spécifié manuellement.
auto counter = [=] () mutable -> int {
    a += b;
    return a;
};
counter(); // 5
counter(); // 7

auto ptr = std::make_unique<int>(42);

// On ne peut pas capturer ptr par valeur (std::unique_ptr ne peut pas être copié).
// On créé donc la variable  'val' dans laquelle 'ptr' est move.
// Cette lambda ne peut pas être copié, seulement move.
auto counter2 = [val = std::move(ptr)] {
    return (*ptr)++;
};
counter2(); // 42
counter2(); // 43
```

Si la lambda n'a pas de capture, elle peut être implicitement cast en pointeur de fonction. Elle peut également prendre des arguments template si ses arguments sont de type `auto`, et être `constexpr`.

```cpp
int sum(int a, int b) {
    return a + b;
};

int main() {
    auto folder = sum;
    // La lambda est convertie en pointeur de type int (*) (int, int).
    folder = [] (int a, int b) {
        return a - b;
    };
    
    auto printer = [] (auto const& value) {
        std::cout << value << '\n';
    };
    printer(42);
    printer("hello");
}
```

### Quelques opérateurs

Voici [une liste des opérateurs disponibles](https://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B), dont ceux qui peuvent être redéfinis.

Lorsque des objets sont créées et détruits avec `new` et `delete`, les opérateurs `operator::new` et `operator::delete` sont appelés. Ces opérateurs ne s'occupent pas de construire ou détruire l'objet, mais uniquement d'allouer et de désallouer sa mémoire.

Ils peuvent également être définis pour les tableaux, avec `operator::new[]` et `operator::delete[]`. Ces opérateurs peut être définis de façon globale ou uniquement pour certaines classes :

```cpp
void* operator new(size_t nb) {
    std::cout << "global operator new called\n";
    return std::malloc(nb);
}
void operator delete(void* ptr) noexcept {
    std::cout << "global operator new called\n";
    std::free(ptr);
}

class Obj {
    static void* operator new(size_t nb) {
        std::cout << "Obj operator new called\n";
        return ::operator new(nb);
    }
};
```

Les opérateurs de comparaison (`==, !=, >, <=, ...`) peuvent être redéfinis. L'opérateur `<` est utilisé par `std::less` pour comparer deux objets : il s'agit d'un *function object*, utilisé par défaut par `std::map`pour trier ses clés.

```cpp
struct Key { int value; };

bool operator<(Key const& lhs, Key const& rhs) {
    return lhs.value < rhs.value;
}

// Signature complète : std::map<
//     Key,
//     std::string,
//     std::less<Key>,
//     std::allocator<std::pair<Key const, std::string>>
std::map<Key, std::string> map {
    { {44}, "world" },
    { {42}, "hello" }
};
```

Les opérateurs arithmétiques (`*, /, -=, ++, ...`) et les opérations sur les bits (`<<, &, |=, ~, ...`) peuvent être redéfinies. `<<` et `>>` sont également utilisés comme opérateurs de flux (pour envoyer ou recevoir des données), comme avec `std::iostream`.

[Les opérateurs litéraux](https://en.cppreference.com/w/cpp/language/user_literal) peuvent s'appliquer sur des chaînes de caractères ou des nombres litéraux. Ils sont souvent utilisés pour avoir une meilleure syntaxe. Les noms des opérateurs litéraux définis doivent commencer avec un *underscore* (les autres noms sont réservés).

```cpp
struct kilometers { double meters; };

kilometers operator "" _km(unsigned long long nb) {
    return kilometers { nb * 3'600.0 };
}

int main() {
    kilometers kms = 10_km;
    
    using namespace std::literals;
    
    // Creates a std::string.
    auto string = "Hello world !"s;
    // Type : std::chrono::hours.
    auto hours = 24h;
    // Type : std::chrono::milliseconds.
    auto milliseconds = 423ms;
}
```

### Pour aller plus loin

L'opérateur & peut être redéfini pour changer la valeur obtenue lorsque l'on prend l'adresse d'un objet. `std::addressof` permet de récupérer l'adresse de l'objet dans tous les cas.

[L'opérateur `new`](https://en.cppreference.com/w/cpp/memory/new/operator_new) peut accepter des paramètres supplémentaires, qui seront passés avec la syntaxe d'un *placement new*.

Boost a une librairie header-only récente, [CallableTraits](https://www.boost.org/doc/libs/1_67_0/libs/callable_traits/doc/html/index.html), qui permet de détecter les arguments et la valeur de retour de *function objects*.

### Challenge

(\*\*) Créer une fonction qui prend un nombre arbitraire de *function objects* en paramètres, et renvoie un *function object* qui met à disposition tous les operateurs `()` des paramètres. Exemple :

```cpp
auto os = std::ofstream{ "logs.txt" };
auto elements = std::tuple{ 42, "hello" };

auto f = make_overload(
    [&os] (int i)           { os << "int = " << i; },
    [&os] (char const* str) { os << "str = " << str; }
);
f(std::get<0>(elements)); // int = 42
f(std::get<1>(elements)); // str = hello
```
