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 }