@certik, I sent a PR for RealDouble class. #439. I did not add any automatic simplification of functions like

`sin`

, `cos`

etc. What's the best way to keep it manageable?
I was thinking of an evaluator class which has methods for all the functions introduced. So if

`sin(x)`

is called where x is a `RealDouble`

or any Number with `is_real`

true, then `x.get_evaluator`

is called to get an evaluator whose `sin`

method will be called.
@isuruf good points. Are you thinking of things like sin(0.0)?

The problem with

`double`

is that it is essentially never exact. In fact, the compiler gives you a warning if you do things like `if (a == 0.0)`

where `a`

is a `double`

.
In other words, there would never be any simplifications.

How do you test that you got 0.0 inside the sin?

It's not just 0.0, I'll check for all instances of

`RealDouble`

and call std::sin on it
Here is a session in Sage:

```
sage: sin(1)
sin(1)
sage: sin(0)
0
sage: sin(0.0)
0.000000000000000
sage: sin(1.0)
0.841470984807897
sage: sin(1.03)
0.857298989188603
```

So Sage takes a double and just immediately evaluates the function using double precision. We can do the same.

How do I do it so that all functions are not needed to be updated each time I add a Real class like RealDouble, RealMPFR, etc.?

Our

`sin`

C++ function, which decides what to do, would just call `std::sin`

if the argument is `RealDouble`

.
So essentially, your question is how to keep all this code local, as opposed to scattered all over symengine, correct?

I think the answer is to use the single dispatch, that we implemented.

So the

`sin`

C++ function would have an array of the callbacks, and it looks up the correct callback based on the argument.
So then the question is how to initialize this array of callbacks --- in eval_double we do it at one place, i.e. the code related to

`RealDouble`

would still be scattered all over symengine.
But I think we can write some tools, so that we can just register these callbacks from one file, realdouble.cpp.

In other words, the default callback table/array will be almost empty, or have some default implementation, and then later we add our custom RealDouble implementation from inside realdouble.cpp.

The only problem in C++ is that the translation units, e.g. realdouble.cpp, can be loaded in any order when the program starts, so there is a problem with initializing the global variables from more than one place.

One workaround is to have "init" functions in symengine --- i.e. that the user would call some kind of

`symengine_init()`

function that would initialize all these internal single dispatch callback tables.
For example by calling

`realdouble_init()`

internally, and then inside this function you register the proper callbacks --- which would work, since all the translation units will be loaded already. But I don't like this too much, I don't think it's very nice to require the user to call these "initialize" functions.
Here is some info: https://isocpp.org/wiki/faq/ctors#static-init-order

How about the way we implemented

`eval_double_single_dispatch`

?
`eval_double_single_dispatch`

uses `table_eval_double`

which is initialized using `init_eval_double()`

. The last function contains all the implementations of the callbacks.
So we would have

`sin_init()`

(to initialize `sin`

callbacks), `cos_init()`

, `tan_init()`

, ...., and inside each of these functions we need to register `RealDouble`

callback and call `std::sin`

, `std::cos`

, `std::tan`

, ...
So all these functions still need to be updated "each time I add a Real class like RealDouble, RealMPFR, etc.".

But I think that's fine, isn't it?

Yes.

Another approach would be to have a 2D table and call it like

Another approach would be to have a 2D table and call it like

`table[SIN][REAL_DOUBLE]`

I don't know if we want to support that, but the single dispatch allows the user to register his callbacks for his custom classes as well --- essentially you just create a unique

`TypeID`

and append a callback into the `std::vector`

, from user code. Things will just work.
I would avoid the static initialization order fiasco for now, and just implement single dispatch as we did for eval_double. We can try to improve upon it later.

One more problem, there are some functions that have two arguments like

`atan2`

, `lowergamma`

I.e. dispatch on values of both arguments using a 2D array.

If we end up having ~300 types in symengine, then the 2D array will have 90000 elements, a pointer (8 bytes) each, so that's ~0.7MB per table/function. I think that's fine.

If the size is a problem, we can implement sparse storage (I assume most of the elements will be some kind of "default"), i.e. a little slower lookup for small memory footprint.