简体   繁体   English

C ++ 11中具有可变参数模板的功能组合

[英]Functional composition with variadic templates in C++11

I'm a mathematician used to doing "old style" C++ programming for a long time now. 我是一名数学家,以前很长一段时间都在做“旧式”C ++编程。 I feel that some new syntactic constructions offerred by C++11 could help me achieve some better code regarding my professionnal projects. 我觉得C ++ 11提供的一些新的语法结构可以帮助我实现一些关于我的专业项目的更好的代码。 Yet as I'm no CS professionnal I must confess that I lack the knowledge to understand some examples I encounter in my self-learning process, altough I've been pretty lucky/succesful so far. 然而,由于我不是CS专业,我必须承认我缺乏了解我在自学过程中遇到的一些例子的知识,尽管到目前为止我还很幸运/成功。

My impression is that variadic templates can be used to implement type-safe functions composition, as in this question . 我的印象是,可变参数模板可用于实现类型安全的函数组合,如本问题所示 My concern is slightly more general since I'd like to compose functions with heterogeneous (but compatible) argument/return types. 由于我想用异构(但兼容)的参数/返回类型编写函数,所以我的关注稍微有点笼统。 I've googled a lot and found another reference , but it seems utter "black magic" to me ;) and I won't pretend I can adapt the code in my context, although I feel I should find what I need in there. 我已经google了很多,并找到了另一个参考 ,但它似乎对我来说是“黑魔法”;)我不会假装我可以在我的上下文中调整代码,虽然我觉得我应该在那里找到我需要的东西。

I think the (most incomplete) code below is relatively self-explanatory as to what I'd like to achieve. 我认为下面的(最不完整的)代码对于我想要实现的内容是相对不言自明的。 In particular I believe the proper implementation will throw a compile-time error when one's trying to compose incompatible functions (Arrow here), and will need a piece of recursive template code. 特别是我相信当一个人试图组合不兼容的函数(这里是箭头)时,正确的实现将抛出编译时错误,并且需要一段递归模板代码。

template <typename Source , typename Target> class Arrow
{
  Target eval (const Source &);
};

template <typename ...Arrows> class Compositor
{
  template <typename ...Arrows>
  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 < A , B >  arrow1;
  Arrow < B , C >  arrow2;

  Compositor< Arrow < A , B > , Arrow < B , C > > compositor(arrow1 , arrow2);

  Arrow < A , C >  expected_result = compositor.arrow();
}

Ideally I'd like 理想情况下,我想
Compositor
to directly subclass 直接子类
Arrow < source_of_first_arrow , target_of_last_arrow>
and the method 和方法
arrow()
be replaced by the corresponding 被相应的替换
eval()

but I felt the above code was more explanatory. 但我觉得上面的代码更具说明性。

Any help will be greatly appreciated, even if it consists in a rough rebuke with a pointer to an existing (relatively basic) piece of example which will surely have escaped my search. 任何帮助将不胜感激,即使它包含一个粗略的指责,指向一个现有的(相对基本的)示例,肯定会逃脱我的搜索。 Thanks! 谢谢!

If I got it correctly, you need no fancy template magic to do this composition. 如果我得到了正确的答案,你就不需要花哨的模板法术来完成这个组合。 Here is the almost self-explanatory code: 这是几乎不言自明的代码:

#include <functional>
#include <string>
#include <iostream>

// it is just an std::function taking A and returning B
template <typename A, typename B>
using Arrow = std::function<B(const A&)>;

// composition operator: just create a new composed arrow
template <typename A, typename B, typename C>
Arrow<A, C> operator*(const Arrow<A, B>& arr1, const Arrow<B, C>& 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<int, float> plusHalf([](int i){return i + 0.5;});
    Arrow<float, std::string> toString([](float f){return std::to_string(f);});

    auto composed = plusHalf * toString; // composed is Arrow<int, std::string>
    std::cout << composed(6) << std::endl; // 6.5

    //auto badComposed = toString * plusHalf; // compile time error
}

I mostly played with lambda functions here. 我主要在这里玩lambda函数。

Using a single function call instead of a operator chain proved to be a more tricky problem. 使用单个函数调用而不是操作符链被证明是一个更棘手的问题。 This time you got some templates: 这次你有一些模板:

#include <functional>
#include <string>
#include <iostream>

// it is just an std::function taking A and returning B
template <typename A, typename B>
using Arrow = std::function<B(const A&)>;

// A helper struct as template function can't get partial specialization
template <typename... Funcs>
struct ComposerHelper;

// Base case: a single arrow
template <typename A, typename B>
struct ComposerHelper<Arrow<A, B>>
{
    static Arrow<A, B> compose(const Arrow<A, B>& arr)
    {
        return arr;
    }
};

// Tail case: more arrows
template <typename A, typename B, typename... Tail>
struct ComposerHelper<Arrow<A, B>, Tail...>
{
    // hard to know the exact return type here. Let the compiler figure out
    static auto compose(const Arrow<A, B>& arr, const Tail&... tail)
    -> decltype(arr * ComposerHelper<Tail...>::compose(tail...))
    {
        return arr * ComposerHelper<Tail...>::compose(tail...);
    }
};

// A simple function to call our helper struct
// again, hard to write the return type
template <typename... Funcs>
auto compose(const Funcs&... funcs)
-> decltype(ComposerHelper<Funcs...>::compose(funcs...))
{
    return ComposerHelper<Funcs...>::compose(funcs...);
}

using namespace std;

int main()
{
    Arrow<int, float> plusHalf([](int i){return i + 0.5;});
    Arrow<float, string> toString([](float f){return to_string(f);});
    Arrow<string, int> firstDigit([](const string& s){return s[0]-'0';});

    auto composed = compose(plusHalf, toString, firstDigit);
    // composed is Arrow<int, int>

    std::cout << composed(61) << std::endl; // "6"

    //auto badComposed = compose(toString, plusHalf); // compile time error
}

Though the output is exactly the same, here I feel Arrow should be its own class so that its domain and range can be stored as types. 虽然输出完全相同,但我觉得Arrow应该是它自己的类,以便它的域和范围可以存储为类型。 Now return types are known without having the compiler deduce them. 现在返回类型是已知的,而无需编译器推导出它们。

#include <functional>
#include <iostream>
#include <string>
#include <sstream>

// Revised as a class with types domain = A and range = B.  It still acts as a std::function<B(const A&)>, through its operator()(const A&) and its data member 'arrow'.
template <typename A, typename B>
class Arrow {
    const std::function<B(const A&)> arrow;
public:
    Arrow (const std::function<B(const A&)>& 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 <typename A, typename B, typename C>
Arrow<A,C> operator * (const Arrow<A,B>& arrow_ab, const Arrow<B,C>& arrow_bc) {
    return Arrow<A,C>([arrow_ab, arrow_bc](const A& a)->C {return arrow_bc(arrow_ab(a));});
}

template <typename First, typename... Rest> struct LastType : LastType<Rest...> {};
template <typename T> struct Identity { using type = T; };
template <typename Last> struct LastType<Last> : Identity<Last> {};

template <typename...> struct ComposeArrows;

template <typename First, typename... Rest>
struct ComposeArrows<First, Rest...> : ComposeArrows<Rest...> {
    using last_arrow = typename LastType<Rest...>::type;
    using composed_arrow = Arrow<typename First::domain, typename last_arrow::range>;
    composed_arrow operator()(const First& first, const Rest&... rest) {
        return first * ComposeArrows<Rest...>::operator()(rest...);
    }
};

template <typename Last>
struct ComposeArrows<Last> {
    Last operator()(const Last& arrow) {return arrow;}
};

template <typename... Arrows>
typename ComposeArrows<Arrows...>::composed_arrow composeArrows (const Arrows&... arrows) {
    return ComposeArrows<Arrows...>()(arrows...);
}

// ------------ Testing ------------
template <typename T>
std::string toString (const T& t) {
    std::ostringstream os;
    os << t;
    return os.str();
}

int main() {
    Arrow<int, double> plusHalf ([](int i){return i + 0.5;});
    Arrow<double, std::string> doubleToString ([](float f){return toString(f) + " is the result";});
    Arrow<std::string, char> lastCharacter ([](const std::string& str)->int {show(str)  return str.back();});

    const Arrow<int, char> composed = composeArrows (plusHalf, doubleToString, lastCharacter);
    std::cout << composed(2) << std::endl; // "t"
    std::cout << composeArrows (plusHalf, doubleToString, lastCharacter)(2) << std::endl; // "t"
}

Here is another solution with a slightly different syntax: 这是另一种语法略有不同的解决方案:

#include <iostream>
#include <functional>
#include <tuple>
#include <string>

template <typename A, typename B>
struct Arrow {
    using domain = const A&;
    using range = B;
    using Function = std::function<range(domain)>;
    const Function& function;
    Arrow (const Function& f) : function(f) {}
    range operator()(domain x) const {return function(x);}
};

template <typename... Ts>
struct LastType {
    using Tuple = std::tuple<Ts...>;
    using type = typename std::tuple_element<std::tuple_size<Tuple>::value - 1, Tuple>::type;
};

template <typename Arrow1, typename Arrow2>
typename Arrow2::range compose (const typename Arrow1::domain& x, const Arrow2& arrow2, const Arrow1& arrow1) {
    return arrow2(arrow1(x));
}

template <typename Arrow, typename... Rest>
auto compose (const typename LastType<Rest...>::type::domain& x, const Arrow& arrow, const Rest&... rest) {
    return arrow(compose(x, rest...));
}

int main() {
    Arrow<std::string, int> f([](const std::string& s) {return s.length();});
    Arrow<int, double> g([](int x) {return x + 0.5;});
    Arrow<double, int> 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
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM