[英]Generalized chaining of non-member functions in C++
我不知道这是否可以实现,但考虑到这些函数\类集:
float plus1(float x) { return x+1; }
float div2(float x) { return x/2.0f; }
template <typename T>
class chain {
public:
chain(const T& val = T()) : val_(val) {}
chain& operator<<( std::function<float (float)> func ) {
val_ = func(val_);
return *this;
}
operator T() const {
return val_;
}
T val_;
};
我可以像这样链接在浮点数上运行的函数:
float x = chain<float>(3.0f) << div2 << plus1 << div2 << plus1;
但是,我想将其概括\扩展为能够在类型之间进行转换并具有 arguments 的功能。不幸的是,我不够聪明,无法弄清楚如何或是否可以做到这一点。 更具体地说,我希望能够做这样的事情(其中operator<<
只是一个任意选择,最好我什至不必在开头写“chain”); 此外,这些只是虚拟示例,我不打算将其用于算术。
std::string str = chain<float>(3.0) << mul(2.0f) << sqrt << to_string << to_upper;
或者
vec3d v = chain<vec3i>(vec3i(1,1,1)) << normalize << to_vec3<double>;
有任何想法吗?
我想我明白你为什么要这样做。 它类似于iostream操纵器。
您将始终需要从chain(...)
(即,您将永远无法神奇地执行int x = 1 << plus(2) << times(2)
),但是您可以重载operator int
, operator float
,...以允许隐式转换。
您还需要返回并定义每种类型(例如mul
),然后实现operator<<
,它需要一个mul或一个const mul,但从总体上讲是可行的(但使用PITA)
这是我针对 C++17 的解决方案。
#include <type_traits>
#include <utility>
template <class F>
struct waterfall
{
waterfall(F&& f)
: fn(std::forward<F>(f))
{}
template <class... Args>
decltype(auto) operator()(Args&&... args) const {
return fn(std::forward<Args>(args)...);
}
template <class T>
auto then(T&& t) const & {
return then_impl(fn, std::forward<T>(t));
}
template <class T>
auto then(T&& t) const && {
return then_impl(std::move(fn), std::forward<T>(t));
}
private:
F fn;
template <class In, class Out>
static auto then_impl(In&& in, Out&& out)
{
auto fn = [in = std::forward<In>(in), out = std::forward<Out>(out)](auto&&... args)
{
using InRet = std::invoke_result_t<In, decltype(args)...>;
if constexpr (std::is_invocable_v<Out, InRet>) {
return out(in(std::forward<decltype(args)>(args)...));
}
else {
in(std::forward<decltype(args)>(args)...);
return out();
}
};
return waterfall<decltype(fn)>(std::move(fn));
}
};
并像这样使用它
int main()
{
// Create a chain
waterfall chain([](const char* s) {
return 42;
})
.then([](auto x) {
// x = 42 here
return x + 1;
})
.then([] {
// Ignoring value from previous function.
// Send double to next one.
return 3.14;
})
.then([](double value) {
// etc...
return true;
});
// chain signature is now bool(const char*)
// Now call our functions in chain
bool ret = chain("test");
}
为了在类型之间进行转换,您希望所有内容都返回一个代理对象,该对象可以转换为任何类型。 也许是基于boost :: variant的东西。
您也可以将运算符<<重写为模板函数,使其更加通用:
template <class UnaryFunction>
chain& operator<<(UnaryFunction func) { _val = func(_val); return *this;}
那将允许您使用任何类型的函数对象作为参数。
要使用带有多个参数的函数,可以使用bind函数。 此功能在C ++ 11之前得到了增强,但现在已成为标准功能,并且可以在任何与C ++ 11兼容的编译器上使用。
使用boost :: proto的通用且可扩展的解决方案:
#include <iostream>
#include <boost/proto/proto.hpp>
namespace bp = boost::proto;
// -----------------------------------------------------------------------------
// perform is a callable transform that take a function_ terminal and execute it
// -----------------------------------------------------------------------------
struct perform : bp::callable
{
template<class Sig> struct result;
template<class This, class Func, class In>
struct result<This(Func,In)>
: boost::result_of<typename boost::remove_reference<Func>::type(In)> {};
template<class Func, class In>
typename result<perform(Func &,In)>::type
operator()( Func& f, In& in ) const
{
return f(in);
}
};
// -----------------------------------------------------------------------------
// Grammar for chaining pipe of functions
// -----------------------------------------------------------------------------
struct pipeline_grammar
: bp::or_<
bp::when<
bp::bitwise_or<pipeline_grammar,pipeline_grammar>
, pipeline_grammar(
bp::_right
, pipeline_grammar(bp::_left,bp::_state)
)
>
, bp::when<
bp::terminal<bp::_>
, perform(bp::_value, bp::_state)
>
> {};
// -----------------------------------------------------------------------------
// Forward declaration of the pipeline domain
// -----------------------------------------------------------------------------
struct pipeline_domain;
// -----------------------------------------------------------------------------
// A pipeline is the top level DS entity
// -----------------------------------------------------------------------------
template<class Expr>
struct pipeline : bp::extends<Expr,pipeline<Expr>, pipeline_domain>
{
typedef bp::extends<Expr, pipeline<Expr>, pipeline_domain> base_type;
pipeline(Expr const &expr = Expr()) : base_type(expr) {}
// ---------------------------------------------------------------------------
// A pipeline is an unary callable object
// ---------------------------------------------------------------------------
template<class Input>
typename boost::result_of<pipeline_grammar(pipeline,Input)>::type
operator()(Input const& in) const
{
pipeline_grammar evaluator;
return evaluator(*this,in);
}
};
// -----------------------------------------------------------------------------
// the pipeline_domain make pipeline expression macthes pipeline_grammar
// -----------------------------------------------------------------------------
struct pipeline_domain
: bp::domain<bp::generator<pipeline>,pipeline_grammar>
{};
// -----------------------------------------------------------------------------
// Takes a PFO instance and make it a pipeline terminal
// -----------------------------------------------------------------------------
template<class Func>
typename bp::result_of::
make_expr<bp::tag::terminal, pipeline_domain,Func>::type
task( Func const& f )
{
return bp::make_expr<bp::tag::terminal,pipeline_domain>( f );
}
//--------------------------- Examples --------------------
struct return_value
{
template<class Sig> struct result;
template<class This, class T>
struct result<This(T)> : bp::detail::uncvref<T>
{};
return_value(int i = 1) : factor(i) {}
template<class T>
T operator()(T const& in) const
{
return in*factor;
}
int factor;
};
struct say_hi
{
typedef void result_type;
template<class T>
void operator()(T const& in) const
{
std::cout << "Hi from value = " << in << "\n";
}
};
int main()
{
return_value r1,r2(5);
(task(r1) | task(r2) | task(say_hi())) (7); // SHould print 35
float k = 10,r;
r = (task(r2) | task(r2) | task(r2) | task(r2))(k);
std::cout << r << "\n"; // Should print 6250
}
基本思想是将功能对象包装为原型终端,构建一个小的| 基于语法,让原型系统处理合成。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.