我是一名数学家,以前很长一段时间都在做“旧式”C ++编程。我觉得C ++ 11提供的一些新的语法结构可以帮助我实现一些关于我的专业项目的更好的代码。然而,由于我不是CS专业,我必须承认我缺乏了解我在自学过程中遇到的一些例子的知识,尽管到目前为止我还很幸运/成功。
我的印象是,可变参数模板可用于实现类型安全的函数组合,如this question中所示。由于我想用异构(但兼容)的参数/返回类型编写函数,所以我的关注稍微有点笼统。我已经google了很多,发现another reference,但它似乎对我来说是“黑魔法”;)我不会假装我可以在我的语境中调整代码,虽然我觉得我应该找到我需要的东西在那里。
我认为下面的(最不完整的)代码对于我想要达到的目标是相对不言自明的。特别是我相信当一个人试图编写不兼容的函数(这里是箭头)时,正确的实现将抛出编译时错误,并且需要一段递归模板代码。
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();
}
理想情况下我想要
Compositor
直接分类为
Arrow < source_of_first_arrow , target_of_last_arrow>
和方法
arrow()
由相应的替换
eval()
但我觉得上面的代码更具说明性。
任何帮助都将受到极大的赞赏,即使它包含一个粗略的指责,指向一个现有的(相对基本的)例子肯定会逃脱我的搜索。谢谢!
答案 0 :(得分:6)
如果我得到了正确的答案,你就不需要花哨的模板法术来完成这个组合。这是几乎不言自明的代码:
#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
}
我主要在这里玩lambda函数。
使用单个函数调用而不是操作符链被证明是一个更棘手的问题。这次你得到了一些模板:
#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
}
答案 1 :(得分:2)
虽然输出完全相同,但我觉得Arrow应该是它自己的类,以便它的域和范围可以存储为类型。现在返回类型是已知的,没有编译器推导出它们。
#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"
}
这是另一种语法略有不同的解决方案:
#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
}