Composition fonctionnelle avec des modèles variadiques en C ++ 11

Je suis un mathématicien habitué depuis longtemps à la programmation C ++ “à l’ancienne”. Je pense que certaines nouvelles constructions syntaxiques proposées par C ++ 11 pourraient m’aider à obtenir un meilleur code pour mes projets professionnels. Cependant, comme je ne suis pas un professionnel de l’informatique, je dois avouer que je ne dispose pas des connaissances nécessaires pour comprendre certains exemples que je rencontre dans mon processus d’autoapprentissage, même si j’ai eu assez de chance / succès jusqu’à présent.

Mon impression est que les modèles variadiques peuvent être utilisés pour implémenter la composition de fonctions de type-safe, comme dans cette question . Ma préoccupation est légèrement plus générale puisque je voudrais composer des fonctions avec des types argument / return hétérogènes (mais compatibles). J’ai beaucoup cherché sur Google et trouvé une autre référence , mais cela me semble une «magie noire» totale;) et je ne prétends pas pouvoir adapter le code à mon contexte, même si j’estime que je devrais y trouver ce dont j’ai besoin.

Je pense que le code (le plus incomplet) ci-dessous est relativement explicite quant à ce que j’aimerais réaliser. En particulier, je pense que la bonne implémentation jettera une erreur de compilation lorsqu’on essaiera de composer des fonctions incompatibles (Arrow ici), et nécessitera un morceau de code de modèle récursif.

template  class Arrow { Target eval (const Source &); }; template  class Compositor { template  Compositor (Arrows... arrows) { // do/call what needs be here }; auto arrow(); // gives a function performing the functionnal composition of arrows }; // define some classes A, B and C int main(int argc, char **argv) { Arrow  arrow1; Arrow  arrow2; Compositor< Arrow  , Arrow  > compositor(arrow1 , arrow2); Arrow  expected_result = compositor.arrow(); } 

Idéalement je voudrais
Compositor
directement sous-classe
Arrow
et la méthode
arrow()
être remplacé par le correspondant
eval()

mais j’ai senti que le code ci-dessus était plus explicatif.

Toute aide sera grandement appréciée, même si elle consiste en une reproche grossière avec un pointeur sur un exemple existant (relativement basique) qui aura sûrement échappé à ma recherche. Merci!

Si je l’ai bien compris, vous n’avez besoin d’aucun modèle de fantaisie sophistiqué pour faire cette composition. Voici le code presque auto-explicatif:

 #include  #include  #include  // it is just an std::function taking A and returning B template  using Arrow = std::function; // composition operator: just create a new composed arrow template  Arrow operator*(const Arrow& arr1, const Arrow& arr2) { // arr1 and arr2 are copied into the lambda, so we won't lose track of them return [arr1, arr2](const A& a) { return arr2(arr1(a)); }; } int main() { Arrow plusHalf([](int i){return i + 0.5;}); Arrow toSsortingng([](float f){return std::to_ssortingng(f);}); auto composed = plusHalf * toSsortingng; // composed is Arrow std::cout << composed(6) << std::endl; // 6.5 //auto badComposed = toString * plusHalf; // compile time error } 

J'ai surtout joué avec des fonctions lambda ici.

L'utilisation d'un appel de fonction unique au lieu d'une chaîne d'opérateurs s'est avérée être un problème plus délicat. Cette fois, vous avez des modèles:

 #include  #include  #include  // it is just an std::function taking A and returning B template  using Arrow = std::function; // A helper struct as template function can't get partial specialization template  struct ComposerHelper; // Base case: a single arrow template  struct ComposerHelper> { static Arrow compose(const Arrow& arr) { return arr; } }; // Tail case: more arrows template  struct ComposerHelper, Tail...> { // hard to know the exact return type here. Let the comstackr figure out static auto compose(const Arrow& arr, const Tail&... tail) -> decltype(arr * ComposerHelper::compose(tail...)) { return arr * ComposerHelper::compose(tail...); } }; // A simple function to call our helper struct // again, hard to write the return type template  auto compose(const Funcs&... funcs) -> decltype(ComposerHelper::compose(funcs...)) { return ComposerHelper::compose(funcs...); } using namespace std; int main() { Arrow plusHalf([](int i){return i + 0.5;}); Arrow toSsortingng([](float f){return to_ssortingng(f);}); Arrow firstDigit([](const ssortingng& s){return s[0]-'0';}); auto composed = compose(plusHalf, toSsortingng, firstDigit); // composed is Arrow std::cout << composed(61) << std::endl; // "6" //auto badComposed = compose(toString, plusHalf); // compile time error } 

Bien que la sortie soit exactement la même, je pense qu’Arrow devrait être sa propre classe afin que son domaine et sa plage puissent être stockés en tant que types. Maintenant, les types de retour sont connus sans que le compilateur les déduise.

 #include  #include  #include  #include  // Revised as a class with types domain = A and range = B. It still acts as a std::function, through its operator()(const A&) and its data member 'arrow'. template  class Arrow { const std::function arrow; public: Arrow (const std::function& a) : arrow(a) {} B operator()(const A& a) const {return arrow(a);} using domain = A; using range = B; }; // The overload * for the composition of two Arrows in Arrow's new form: template  Arrow operator * (const Arrow& arrow_ab, const Arrow& arrow_bc) { return Arrow([arrow_ab, arrow_bc](const A& a)->C {return arrow_bc(arrow_ab(a));}); } template  struct LastType : LastType {}; template  struct Identity { using type = T; }; template  struct LastType : Identity {}; template  struct ComposeArrows; template  struct ComposeArrows : ComposeArrows { using last_arrow = typename LastType::type; using composed_arrow = Arrow; composed_arrow operator()(const First& first, const Rest&... rest) { return first * ComposeArrows::operator()(rest...); } }; template  struct ComposeArrows { Last operator()(const Last& arrow) {return arrow;} }; template  typename ComposeArrows::composed_arrow composeArrows (const Arrows&... arrows) { return ComposeArrows()(arrows...); } // ------------ Testing ------------ template  std::ssortingng toSsortingng (const T& t) { std::ossortingngstream os; os << t; return os.str(); } int main() { Arrow plusHalf ([](int i){return i + 0.5;}); Arrow doubleToSsortingng ([](float f){return toSsortingng(f) + " is the result";}); Arrow lastCharacter ([](const std::ssortingng& str)->int {show(str) return str.back();}); const Arrow composed = composeArrows (plusHalf, doubleToSsortingng, lastCharacter); std::cout << composed(2) << std::endl; // "t" std::cout << composeArrows (plusHalf, doubleToString, lastCharacter)(2) << std::endl; // "t" } 

Voici une autre solution avec une syntaxe légèrement différente:

 #include  #include  #include  #include  template  struct Arrow { using domain = const A&; using range = B; using Function = std::function; const Function& function; Arrow (const Function& f) : function(f) {} range operator()(domain x) const {return function(x);} }; template  struct LastType { using Tuple = std::tuple; using type = typename std::tuple_element::value - 1, Tuple>::type; }; template  typename Arrow2::range compose (const typename Arrow1::domain& x, const Arrow2& arrow2, const Arrow1& arrow1) { return arrow2(arrow1(x)); } template  auto compose (const typename LastType::type::domain& x, const Arrow& arrow, const Rest&... rest) { return arrow(compose(x, rest...)); } int main() { Arrow f([](const std::ssortingng& s) {return s.length();}); Arrow g([](int x) {return x + 0.5;}); Arrow h([](double d) {return int(d+1);}); std::cout << compose("hello", g, f) << '\n'; // 5.5 std::cout << compose("hello", h, g, f) << '\n'; // 6 }