Boost logo

Boost :

From: Max Motovilov (max_at_[hidden])
Date: 2004-02-25 17:41:42


This is a long post, so please let me know if making it into a Web page and
posting the URL is a better idea.

=======================

[Terminology]

Since I am not sure whether there is an accepted term for this kind of
types, let me introduce one - "derivative arithmetic type":

- D.A.T. is a templatized ADT with at least some arithmetic operations
defined
- D.A.T. is parameterized with a single type-parameter which has at least
some arithmetic operations defined (usually it is a built-in numeric type,
but not necessarily)
- Arithmetic operations on D.A.T. are defined in terms of arithmetic
operations on the base type
- There is a conversion available between differently parameterized
instances of D.A.T.

The above certainly isn't a good "standardese" concept definition but it
should be enough to illustrate the proposal.

Examples of D.A.T.s are std::complex<>, boost::rational<>,
boost::math::quaternion<> etc. However, the motivation for this proposal
came from my experience with implementing geometric types such as point,
rectangle etc. along with operations on them.

[Proposal, Part I]

Providing a facility that simplifies arithmetic type promotions for D.A.T.s.
None of the examples above currently provide it, which means that you cannot
expect

std::complex<float>( 1, 0 ) + std::complex<double>( 0, 1 )

to yield std::complex<double>( 1, 1 ). So, expressions on D.A.T.s with mixed
base types do not really behave the way the built-in numeric types do, even
though the individual values can be converted from one instance of the same
D.A.T. to another.

In the ideal world, one should be able to write:

template< typename T1, typename T2 >
foo< typeof( T1() + T2() ) >
    operator+( const foo<T1>&, const foo<T2>& );

and then implement operator+ the way it is normally implemented for foo<T>,
first converting foo<T1> and foo<T2> to foo< typeof< T1() + T2() >. I
intentionally leave out the optimization issues such as defining operator+
in terms of operator+= etc. However, without typeof(), it would be nice to
have a library solution yielding similar result, for example:

template< typename T1, typename T2 >
foo< promoted_type< T1, T2, std::plus > >
    operator+( const foo<T1>&, const foo<T2>& );

The third argument of promoted_type<> could be any kind of tag value or type
associated with the type of arithmetic operation. Templates std::plus,
std::minus, std::multiplies, std::divides and std::modulus do not cover the
entire range of C++ arithmetic operations so some other tags may work
better. The implementation of promoted_type will obviously be based on
partial specializations along with a limited typeof() facility with the
assumption that T1 op T2 always yields either T1 or T2 (which is the case
for built-in base types). Here is a quick and dirty implementation for only
one operation, operator+:

template< typename T1, typename T2 > struct typeof_one_of_two
{
    template<int N> struct select_type;
    template<> struct select_type<1> { typedef T1 type; };
    template<> struct select_type<2> { typedef T2 type; };
    static char (&typeof_fun( const T1& ))[1];
    static char (&typeof_fun( const T2& ))[2];
};

template< typename T > struct typeof_one_of_two<T,T>
{
    template<int N> struct select_type { typedef T type; };
    static char typeof_fun( const T& );
};

template<
    typename T1, typename T2,
    template< typename _ > class Tag
> struct promoted_type;

template< typename T1, typename T2 > struct promoted_type< T1, T2, std::plus
>
{
    enum { tag = sizeof( typeof_one_of_two<T1,T2>::typeof_fun( T1() +
T2() ) ) };
    typedef typename typeof_one_of_two<T1,T2>::select_type< tag >::type
type;
};

(I tried it on MSVC++ 7.1 and it works, though for some reason it doesn't
compile if I drag the expression for promoted_type<>::tag into the
declaration for promoted_type<>::type. With the intermediate enum value it
works fine - a compiler bug?)

Note that promoted_type<> may be used not just in binary operators but in
any external function or a D.A.T. method, possibly with different D.A.T.
parameters, as long as one can make a reasonable deduction of the result
type based on certain arithmetic operation over base type values. Also note
that all built-in binary operators defined for a pair of built-in numeric
types produce results of the same type. This means we wouldn't really need
3rd parameter for promoted_type<> if the base types were always built-in
numerics, but user-defined base types may break this assumption.

[Proposal, Part II]

Going further, we might want to add type-promoting arithmetics to existing
DATs that do not provide it. The best I could think of so far is introducing
overly generic template operators into global namespace, along with a
mechanism based on boost::enable_if to restrict their use to specific
parameter templates. The model implementation follows. I find it ugly, but
perhaps a better one may be immediately obvious to somebody else.

template< template< typename _ > class DAT > struct promote_sum
{
    typedef void tag;
};

template< typename P, typename E=void > struct do_not
{
    enum { value = false };
};

template< typename P > struct do_not< P, typename P::tag >
{
    enum { value = true };
};

template< typename T1, typename T2, template< typename _ > class DAT >
inline typename boost::disable_if_c<
    do_not< promote_sum< DAT > >::value,
    DAT< typename promoted_type<T1,T2,std::plus>::type >
>::type
    operator+( const DAT<T1>& a, const DAT<T2>& b )
{
    typedef typename DAT< promoted_type<T1,T2,std::plus>::type > T;
    return T( a ) + T( b );
}

To enable promoted-type operator+ for a specific DAT (that already has some
flavor of operator+ though without mixing base types), write:

template<> struct promote_sum< MyDAT > {};

The use, but not implementation, is similar to the technique suggested by
David Abrahams in boost::operators. This one is uglier but, I believe,
standard-compliant. Template do_not<> does a SFINAE trick of its own because
I couldn't figure out how to do the same thing with enable_if. And again,
MSVC++ 7.1 swallows it whole. Here's what I used to test it:

template<typename T> struct MyDAT_1
{
    friend MyDAT_1<T> operator+ ( const MyDAT_1<T>&, const MyDAT_1<T>& );
    MyDAT_1( T );
    template<typename T1> explicit MyDAT_1( const MyDAT_1<T1>& );
};

template<typename T> struct MyDAT_2
{
    friend MyDAT_2<T> operator+ ( const MyDAT_2<T>&, const MyDAT_2<T>& );
    MyDAT_2( T );
    template<typename T1> explicit MyDAT_2( const MyDAT_2<T1>& );
};

template<> struct promote_sum<MyDAT_1> {};

Here MyDAT_1 and MyDAT_2 are identical types; both define an operator+ for
uniform arguments. Promoted operator+ is only enabled for MyDAT_1. Now,

MyDAT_1<double> a(( MyDAT_1<int>( 1 ) + MyDAT_1<float>( 2.0 ) ));

compiles, while

MyDAT_2<double> b(( MyDAT_2<int>( 1 ) + MyDAT_2<float>( 2.0 ) ));

produces the following error:

e:\tmp\t.cpp(77): error C2678: binary '+' : no operator found which takes a
left-hand operand of type 'MyDAT_2<T>' (or there is no acceptable
conversion)
        with
        [
            T=int
        ]

which looks rather on-the-money to me.

The example implementation above only covers operator+( Foo<T1>, Foo<T2> ).
It has an obvious extension to all operator@( Foo<T1>, Foo<T2> ) as well as
to other things, for example:

template<> struct promote_product2< Matrix, Vector > {};

which can be implemented in the same way. An important special case is
support for things like operator*( Matrix<T1>, T2 ) where T2 is presumed to
be a scalar type and Matrix<T1>*T2 is expected to yield a Matrix<
promoted_type< T1, T2, std::multiplies > >. A generic operator* can be
provided for this case but it will only discriminate based on its first
argument, which may be dangerously generic for a general-purpose facility...

=================================

Well, if anybody got this far, I have to confess that I'm not sure what to
do with all this. The first part looks so trivial I may have well overlooked
an equivalent facility in one of the existing Boost libraries. If I haven't,
it hardly deserves a separate library, but I'm not sure which existing one
would be a good place for it. The second part looks like it belongs in
boost::operators, assuming that its benefits outweigh the dangers of
introducing very generic operators into global namespace (or is there a way
to do it otherwise and still get C++ to find the operator definitions?). Let
me know whether it will be worthwhile to develop proposed facilities for
subsequent Boostification.

Regards,
...Max...


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk