📜 ⬆️ ⬇️

Dynamic mat. functions in C ++

Hello, Habrayuzer.
I recently read here an article about anonymous functions in C ++, and right there in my mind I had a thought: I urgently need to write a class to work with functions that we know from mathematics. Namely, taking a real argument and returning the same real value. It is necessary to give the opportunity to handle such objects as simply as possible, without thinking about the implementation.
And that's how I implemented it.

Problem.

All these lambda expressions behave at times rather strange, at least for me. This is probably due to the fact that I cannot fully understand how the mechanism for creating these expressions works. I will create large functions on the basis of already existing primitive actions, i.e. something like f (x) = a (x) + b (x). And this means that all lambdas, created even only as intermediate links in the construction of functions, must be retained for reference to them. Now I will try to explain more clearly.

Let's say we want a procedure to take a pair of functions A and B and return a new expression, for example, 5A + B. Our procedure will create a 5A lambda, then create 5A + B using 5A and B. The resulting procedure will return and complete this The moment the lambda 5A disappears from the scope, and the returned expression simply will not work.

Decision.

I decided to create one global collection of all lambdas, all constructed expressions are stored in it, and the objects will contain only pointers to the elements of the collection. I will explain the code:
')
#include <xstddef> #include <functional> #include <list> typedef std::tr1::function<double(double)> realfunc; // y = f(x) as in maths class func_t { protected: static std::list<realfunc> all_functions; //   realfunc *f; //    public: func_t(); func_t(const double); func_t(const realfunc&); func_t(const func_t&); ~func_t() {}; friend func_t operator+ (const func_t&, const func_t&); friend func_t operator- (const func_t&, const func_t&); friend func_t operator* (const func_t&, const func_t&); friend func_t operator/ (const func_t&, const func_t&); friend func_t operator^ (const func_t&, const func_t&); func_t operator() (const func_t&); double operator() (const double); }; 


For a start, the designers .
The default constructor (without parameters) will create a function that returns an argument. This will be the starting point. f (x) = x.
The rest is clear: the second creates a constant function - f (x) = c, the third turns the desired type of lambda into an object of my class, the last is just a copy constructor.

The implementation of constructors, it will immediately show how all the methods of the class are arranged:

 func_t::func_t() { f = &(*all_functions.begin()); } func_t::func_t(const double c) { func_t::all_functions.push_back( [=](double x)->double {return c;} ); this->f = &all_functions.back(); } func_t::func_t(const realfunc &realf) { func_t::all_functions.push_back(realf); this->f = &all_functions.back(); } func_t::func_t(const func_t &source) { this->f = source.f; } 


As you can see, I create a lambda, push it to the end of the collection and return an object with a pointer to it.
I want to immediately explain the first designer. As I said before, the creation of an “argument”, i.e. functions f (x) = x is the beginning of almost any work with my class, so I decided to emphasize this function and put this expression in the first cell of the collection. And then when you call the default constructor, the object always gets a pointer to the first element of the collection.

Oh yeah, I almost forgot, all designers can be used for implicit conversion, which creates the main usability.

Next, the operators . Everything is simple with them. I will show three of them: the first implements addition, the second composition of functions, the third estimate.

 func_t operator+ (const func_t &arg, const func_t &arg2) { realfunc realf = [&](double x)->double { return (*arg.f)(x) + (*arg2.f)(x); }; return func_t(realf); } func_t func_t::operator() (const func_t &arg) { realfunc realf = [&](double x)->double { return (*f)((*arg.f)(x)); }; return func_t(realf); } double func_t::operator() (const double x) { return (*f)(x); } 


It's simple, right? In the first two, the necessary lambda is created again, and then sent to the object constructor. In the third method in general freebie =)
I will clarify that I use the environment passing by reference everywhere (this is [&] before lambda) to access the method arguments.

Basically, that's all. Now a couple of technical details. I am not very good at initializing static fields, so I had to remember the terribly cumbersome and ugly methods that I had once spied somewhere.

 protected: static class func_t_static_init_class { public: func_t_static_init_class(); }; static func_t_static_init_class func_t_static_init_obj; ... //Static: std::list<realfunc> func_t::all_functions = std::list<realfunc>(); func_t::func_t_static_init_class func_t::func_t_static_init_obj = func_t::func_t_static_init_class(); func_t::func_t_static_init_class::func_t_static_init_class() { func_t::all_functions.push_back( [](double x)->double {return x;} ); } 


Well, you understand, create a list and shove into it the first element, which I have already mentioned.
I apologize for this horror, just learning to program.

Bonuses

Basically, that's all. There remained a couple of things that I did already purely for interest (although, in fact, like everything).

First, overload a couple of functions from cmath.
 friend func_t sin(const func_t&); friend func_t cos(const func_t&); friend func_t tan(const func_t&); friend func_t abs(const func_t&); ... func_t sin(const func_t& arg) { realfunc realf = [&](double x)->double { return sin((*arg.f)(x)); }; return func_t(realf); } 


Secondly, where do without derivatives and primitives =)

 static double delta_x; func_t operator~ (); func_t operator| (const double); ... double func_t::delta_x = 0.01; func_t func_t::operator~ () { realfunc realf = [&](double x)->double { return ((*f)(x + delta_x / 2) - (*f)(x - delta_x / 2)) / delta_x; }; return func_t(realf); } func_t func_t::operator| (double first_lim) { realfunc realf = [=](double x)->double { double l_first_lim = first_lim; //will move with this copy of first_lim double area = 0; bool reverse = x < first_lim; //first_lim > second_lim? if (reverse) { l_first_lim = x; x = first_lim; } double l_delta_x = delta_x; //step while (l_first_lim < x) { //move along the whole span if ((l_first_lim += l_delta_x) > x) //stepped too far? l_delta_x += x - l_first_lim; //the last l_delta_x may be shorter /* integral summ, the point is chosen between the point for f(x) is chosen between l_first_lim and l_first_lim + l_delta_x */ area += l_delta_x * (*f)(l_first_lim + l_delta_x / 2); } return area * (reverse?-1:1); }; return func_t(realf); } 


I will not go into the explanation of this code, it is boring and is an absolutely naive implementation of the derivative at a point and the integral with a variable upper limit (in both cases, instead of the limit, a fixed delta is used).

Conclusion

Well, that's all. Hope that was interesting. I don’t know if something has any meaning at all, but I practiced in classes, all sorts of & and *, but for me this is the main thing =) Thank you for your attention.

Oops! Something else.

Well, yes, how to use it.
For example, like this:
 func_t f1 = cos(5 * func_t() + 8); 

this will create, as you can see, the function
f1 (x) = cos (5x + 8)

or like this:
 funt_t x = func_t(); func_t f = x + f1(x / ~f1); 

this f (x) = x + f1 (x / f1` (x)) cap passed by

or in the end like this:
 realfunc g = [](double->double) { ... //   ,    ... } func_t f3 = g; 

Conclusion number 2.

Well, now for sure. Thanks again for your attention!
If anything, the complete and unfinished code is here .

Source: https://habr.com/ru/post/149450/


All Articles