
Boost : 
From: Aleksey Gurtovoy (agurtovoy_at_[hidden])
Date: 20040315 03:13:29
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 compiletime 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
> compiletime 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
userdefined 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 "containsitall" numeric type  simply because there is no
such type (for instance, 'fixed_c' is not capable of holding
compiletime complex numbers, and so on). Moreover, specializing of
arithmetic metafunctions is not necessary a prerogative of numeric types
 consider, for example, randomaccess 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
> compiletime constants without having to manually convert them to
> similar types beforehand (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<N1,N2>,N3 >,N4 >,N5 >
{
};
// ...
template<
typename N1
, typename N2
>
struct plus<N1,N2,int_<0>,int_<0>,int_<0> >
: plus2<N1,N2>
{
};
>
> 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 tagdispatched 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<N1,N2>::type
{
};
// 'plus_impl' implemenation for plain integral constants
template<>
struct plus_impl<integral_c_tag,integral_c_tag>
{
template< typename N1, typename N2 > struct apply
: integral_c<
BOOST_MPL_AUX_TYPEOF(N1,N1::value + N2::value)
, (N1::value + N2::value)
>
{
};
};
where 'plus_tag' is something as simple as
template< typename T > struct plus_tag
{
typedef typename T::tag type;
};
2) Provide a rank mechanism so that we know in which direction to cast
in case of mixed operations; can be as simple as an integralconstant
'rank' member of the corresponding tag:
plus_tag< int_<5> >::type::rank == 0
3) Provide default implementations of '*_impl' binary templates which
would use ranks, tags, and 'numeric_cast' to implement a general scheme
for mixed arithmetics/comparison, something along these lines:
template<
bool c_
, typename Tag1
, typename Tag2
>
struct plus_impl_
{
template< typename N1, typename N2 > struct apply
: plus_impl<Tag2,Tag2>::template apply<
typename apply< numeric_cast<Tag2,Tag1>, N2>::type
, N2
>
{
};
};
template<
typename Tag1
, typename Tag2
>
struct plus_impl_<false,Tag1,Tag2>
{
template< typename N1, typename N2 > struct apply
: plus_impl<Tag1,Tag1>::template apply<
N1
, typename apply< numeric_cast<Tag1,Tag2>, N2>::type
>
{
};
};
template<
typename Tag1
, typename Tag2
>
struct plus_impl
: plus_impl_< ( Tag1::rank < Tag2::rank ),Tag1,Tag2 >
{
};
Does it make any sense to you?
P.S. I suggest we move the discussion to the developers list (I'm going
to crosspost this one).
 Aleksey Gurtovoy MetaCommunications Engineering
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk