Matt Calabrese wrote:
Okay, I decided before I continue with my other library, I'll fully implement rational_c properly (and possibly fixed_c), however, doing so without making the code horribly complex would require changes to the current definitions of pretty much all of the operations (plus, multiplies, etc. as well as the conditional operators). ^^^^^^^^^^^ I suppose you meant "comparison", here and below.
The problem is that those current functions take 5 parameters, making specialization nearly impossible for the rational and fixed templates as you'd have to account for when the 3rd, 4th, 5th, etc parameters are not passed (and so the default integral 0 is used). That can't be easily done (at least I don't see how) without having to specialize each condition separately (one for 3 rational types, one for 4 rational types, etc ). Moreso, this applies to the conditional operators as well, there is no easy way to account for using different types in multiplies IE multiplying a rational and integral together, or a fixed and rational, etc. Again, to account for these possibilities you'd have to make an incredibly large amount of specializations. Of course, you could always just limit them to only work when all the parameters are the same kind of compile-time value template, but that would be somewhat limitting,especially with other solutions available.
I pretty much agree with the analysis.
What I propose is a way around this situation, but still have the metafunctions declared the same way, therefore not breaking old code and not limiting functionality. To do so, the following adjustments would have to be made (and I have already begun to make them for my own use):
1) Additional versions of all of the arithmetic operators which take 2 parameters, each need to be defined and work like binary operators. They should be specialized for when both the left and right operands are of the same type. How to account for when they are different types will be covered in the future points.
Sounds good so far.
2) A value_cast metafunction which can cast between all different compile-time constant types. The way it is implemented is by requiring all types to have specialized metafunctions for converting to and from the fixed point type. When value_cast is used, it, by default, converts the right hand operand to fixed_c, then converts that fixed_c to the target type. Since a fixed_c can hold pretty much any real value to a certain precision, you can be certain, to an extent, that little, if any precision will be lost through conversion. So, with that proposed addition, you'd be able to convert between all different types and only require people developing new types to have to specialize conversion to and from fixed_c. The only problem is that in order for the value_cast to exist, it obviously needs to have a parameter which specifies a type to convert to. The only way I can currently see that would allow that would be to have the value_cast take a type parameter which would be a template instantiation with a dummy value, which would, in a way, be very similar to rebinding allocators.
The idea of avoiding a combinatorial explosion through a single user-defined conversion metafunction which will be used to even the argument types is definitely solid; however, I am afraid we cannot escape with something as simplistic as conversion to a default predefined "contains-it-all" numeric type -- simply because there is no such type (for instance, 'fixed_c' is not capable of holding compile-time complex numbers, and so on). Moreover, specializing of arithmetic metafunctions is not necessary a prerogative of numeric types -- consider, for example, random-access iterators. To summarize, IMO the idea is excellent, but it needs to be carried a little bit further -- please see below.
3) The conditional operations and 2 operand arithmetic operations should be redefined in the base template definition to convert both types to fixed_c and then make a comaprison, therefore allowing programmers to use the operations on two different types of compile-time constants without having to manually convert them to similar types before-hand (as I alluded in first point). The conditional operations can then be specialized for when the types are the same on each side to avoid the cost of a cast on each of the operands to fixed_c.
Yep to everything except the 'fixed_c' part.
4) The current "varying length" operations which cause problems (multiplies, plus, minus, etc) should be redefined to just call the 2 operand version of the corresponding operation on each of the parameters passed to the operation. This way, programmers can still pass multiple parameters to these functions, with each parameter being a different type, without the developer having to make an extremely large amount of specializations for each type (including those which have yet to be developed).
Yep! Something like
template<
typename N1
, typename N2
, typename N3 = int_<0>
, typename N4 = int_<0>
, typename N5 = int_<0>
>
struct plus
: plus2< plus2< plus2< plus2
I have already begun implementing all of the points I have suggested, though with my rational_c as a the basis for conversion rather than fixed_c (as I haven't yet developed fixed_c). Also, converting to and from rational_c is considerably simpler than converting to and from fixed_c. For this reason, I may end up leaving the basis for conversion as rational_c instead of fixed_c.
If anyone sees any other, more elegant solutions for the problems I mentioned, please post them and I may change my approach. Additional suggestions are also greatly appreciated.
You are going in the right direction. Here are some suggestions of how
to generalize the scheme to get what I would consider an ideal solution:
1) Make specializable binary templates for each arithmetic/comparison
metafunctions to be tag-dispatched metafunction classes, e.g.
template<
typename N1
, typename N2
>
struct plus2
: plus_impl< // 'plus_impl' is a point of customization
typename plus_tag<N1>::type
, typename plus_tag<N2>::type
>::template apply