Templates + fonctionnel

Le problème exposé dans ce sujet a été résolu.

Salut les agrumes !

J'ai un petit soucis avec C++ qui commence à m'embêter, et je me demandais si quelqu'un avait une solution élégante pour le résoudre.

L'idée est de créer un genre de décorateur (pour le pythonistes) ou bien de fonction wrapper pour augmenter le comportement d'un morceau de code spécifique. Pour l'illustration, disons que je veux pouvoir afficher le résultat d'une fonction de double vers double. Pour ça, pas de problème, je peux simplement faire :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <iostream>
#include <functional>
#include <cstdlib>

using namespace std;

void PrintUnary(function<double(double)> F, double arg){
  cout << F(arg) << endl;
}

int main(void){
  PrintUnary([] (double arg) -> double {return arg + 1;}, 3);
  return EXIT_SUCCESS;
}

En revanche, si j'essaie de résoudre le problème en utilisant des templates, impossible de compiler. Si je déclare :

1
2
3
4
template<function<double(double)> F>
void PrintUnary(double arg){
  cout << F(arg) << endl;
}

Le compilateur me dit que function<double(double)> n'est pas un type convenable pour ma fonction template.

Et si j'essaie de déclarer ça "à la C" :

1
2
3
4
5
6
7
8
9
template<double(*F)(double)>
void PrintUnary(double arg){
  cout << F(arg) << endl;
}

int main(void){
  PrintUnary<[] (double arg) -> double {return arg + 1;}>(3.);
  return EXIT_SUCCESS;
}

Cette fois-ci c'est le type de la lambda-expression du main qui n'est pas compatible avec le type de ma fonction template.

Ma question est donc : n'y a-t-il aucun moyen en C++ de paramétrer du code avec une valeur fonctionnelle et en utilisant les templates ? Je trouve ça bizarre que ces deux notions très importantes de C++ ne soient pas compatibles.

Dans ton premier code, tu définis la fonction PrintUnary, mais tu appelles unaryFun dans main.

Ensuite, tu utilises incorrectement le template. Entre les chevrons d'un template, tu ne peux mettre que deux sortes d'arguments : soit un type générique (par exemple typename T), soit une variable nommée, qui doit être d'un type POD. Les POD, ça correspond grosso modo aux types de base, int, double, etc. Donc std::function ne rentre pas dans les clous.

Si tu veux templater ta fonction, tu pourrais écrire ce qui suit.

1
2
3
4
template<typename TRetour, typename TArgument>
void unaryFun(std::function<TRetour(TArgument)> F, TArgument arg) {
  cout << F(arg) << endl;
}

De cette manière, tu peux afficher le résultat de n'importe quelle fonction à un seul argument, quels que soient le type de l'argument et le type de retour de la fonction.

+1 -0

Quel serait l'utilité de passer l'argument F au template plutôt que directement à la fonction ? Tu n'utilises pas le template pour ce pour quoi il a été conçu, puisque ta fonction unaryFun ne prendra qu'une seule forme, ton programme sera plus lent, parce que pour chaque F, il devra trouver quelle version surchargée de unaryFun il doit appeler, et enfin, tu ne pourras jamais mettre ton code dans une bibliothèque dynamique, parce que unaryFun est templatée.

Pour rappel, l'utilité d'un template, c'est d'écrire en une seule fois plusieurs fonctions acceptant les mêmes arguments mais de types différents, par d'écrire une seule fonction pouvant recevoir des arguments de valeurs différentes. Cela revient à utiliser des types paramétrés dans un langage fonctionnel.

Par exemple, en Haskell, la fonction telle que tu la définis s'écrirait comme ça.

1
2
unaryFun :: (Double -> Double) -> Double -> Double
unaryFun f arg = f arg

Comme tu le vois, aucun type paramétré n'est utilisé. Alors que ma fonction s'écrirait ainsi.

1
2
unaryFun :: (a -> b) -> a -> b
unaryFun f arg = f arg

En résumé, pense ta fonction dans un langage fonctionnel : si tu n'utilises pas de types paramétrés pour l'écrire, il y a peu de chances que tu aies besoin d'un template pour l'écrire en C++.

+2 -0

J'ai rarement envie d'écrire ce genre de choses je te rassure. Je voulais juste voir jusqu'où je pouvais utiliser les templates après avoir vu qu'on pouvais passer directement des int par exemple (en TD, on s'est amusé à créer des structures template récursives qui calculaient factorielle par exemple).

A noter, ce qu'on veut c'est juste un truc qui peut être exécuté à partir des arguments donnés et afficher le résultat. Donc :

1
2
3
4
5
6
7
8
9
template<class Func, class ...Types>
void do_print(Func f, Types ...args){
    auto result = f(args...);
    std::cout<<result<<std::endl;
}

int main(){
  do_print([](double arg){return arg + 1.;}, 3);
}

Du coup f peut aussi être un objet dont le type surcharge (), etc.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte