Boost logo

Boost :

From: Gennaro Prota (gennaro_prota_at_[hidden])
Date: 2003-06-20 10:56:51


On Thu, 19 Jun 2003 19:51:31 +0100, "Paul A Bristow"
<boost_at_[hidden]> wrote:

>| Well, you wanted to know what is likely to be accepted. In a
>| formal review (this isn't anymore, AFAIU, is it?) I would
>| vote no to your approach.
>
>But would you vote yes if the only presentation was Daniel's method?

Overall? What I like in his code is the use of template specialization
to provide the values for different types, and the possibility to add
UDTs. OTOH, I'm not sure whether the conversion function template and
the pre-built binary operators (see ADD_OPERATOR) are worth having.
Personally I won't probably use those operators, as they are just
syntactic sugar to avoid an explicit cast of the constant instance to
the type of its (left or right) "neighbour". Of course that's
different from what we are used to with built-in constant variables
(floating point promotion). Example: if I have

 const double pi = ...;
 
then in

 pi * f * d

the language assures me that f is promoted to a double as well and the
result is a double. When I use "class pi" instead the selected type of
the constant (through pi_value) becomes the one of the adjacent
operand, which is quite a different thing. So you have a familiar
appearance (you write the expression as if 'pi' had a built-in type,
with the usual operators etc.) with an unfamiliar behavior. I'm not
saying that is necessarily wrong but, not having any concrete
experience with it, I'm not sure it is a good thing
either. However, one can explicitly static_cast, so those who want to
be "safe" can be.

Of course, if you leave there the conversion function but not the
operators you end up with the same behavior in most cases, thanks to
the built-in candidates in overload resolution. And a conversion
function can't be made 'explicit' (for now?). A possibility could be
to use some "floating point promotion" traits (one trivial
implementation is in the Yahoo files section), to select the
*promoted* neighbour type, but that's probably not worth the trouble.

Summarizing: I think Daniel is on the right track, but I'm not
particularly fond of the automatic "conversions" (actually they are
automatic "selections" of the type, based on the context of usage -
and since the selection mechanism is different from the built-in
one...)

>I am only really concerned that the accurate constants become more
>standardised, in name and value.

Yes. Just to stimulate discussion, and without any offence towards
Daniel's solution, this is an approach without the conversion function
and the operator overloads. Beware that it's completely untested.

namespace math
{
    // Generic base class for all constants
    template< typename T /*, template< class > class F*/ >
    struct constant {};

    // value_type selector basically says what's the
    // template class (pi_value, gamma_value, etc.) whose
    // specializations give the different values. It avoids
    // using template template parameters, which aren't
    // supported by all compilers.

    template<typename T>
    struct value_selector;

    template <typename U, typename T>
    U as(const constant<T> & c) {
        value_selector<T>::type<U>::spec obj;
        return obj();
    };

    // Here's the definition for pi for some
    // usual types (can be extended by UDTs)
    //
    template<typename T> struct pi_value;

    template<> struct pi_value<float>
        { float operator()() { return 3.14f; } };

    template<> struct pi_value<double>
        { double operator()() { return 3.1415; } };
    
    template<> struct pi_value<long double>
        { long double operator()() { return 3.141592L; } };

    // Here's the single line to create a useful interface
    struct pi_type : constant< pi_type > {} pi;

    // Here's the line to tell that the value of type T
    // for the constant pi is given by pi_value<T>
    template <> struct value_selector<pi_type>
    {
        template <typename U>
        struct type {
            typedef pi_value<U> spec;
        };
    
    };

    //---------------------------------
    // another constant (pi ** 2)
    template< typename T > struct pi2_value;
    template<> struct pi2_value<float>
        { float operator()() const { return 9.87f; } };
    template<> struct pi2_value<double>
        { double operator()() const { return 9.8696; } };
    template<> struct pi2_value<long double>
        { long double operator()() const { return 9.8696044L; } };

    struct pi2_type : constant<pi2_type> {} pi2;

    template <> struct value_selector<pi2_type>
    {
        template <typename U>
        struct type {
            typedef pi2_value<U> spec;
        };
    
    };

    //---------------------------------------
    // Some obvious (?) constants:
    //
#define CONSTANT_VALUE( name, value ) \
    template< typename T > struct name##_value \
        { T operator()() const { return value; } }; \
                                                        \
    struct name##_type : constant<name##_type> {} name; \
                                                        \
    template<> \
    struct value_selector<name##_type> { \
        template <typename U> struct type { \
            typedef name##_value<U> spec; \
        }; \
    }; /**/

    CONSTANT_VALUE( minus_one, -1 );
    CONSTANT_VALUE( zero, 0 );
    CONSTANT_VALUE( one, 1 );
    CONSTANT_VALUE( two, 2 );
    CONSTANT_VALUE( three, 3 );
    CONSTANT_VALUE( ten, 10 );
#undef CONSTANT_VALUE
    

    // And their relationship:
    pi2_type operator*( const pi_type&, const pi_type& )
    { return pi2_type(); }

}

#include <cmath>
namespace std {
    
    //---------------------------------------
    // example: exact value of a function
    math::pi_type acos(math::minus_one_type /* no parameter name */ )
    {
        return math::pi_type();
    }

};

#include <iostream>
#include <ostream>
using namespace std;

int main()
{
    // Usage example:
    using math::pi;
    using math::pi2;
    using math::two;

    cout.precision(10);

    cout << math::as<double>(pi) << '\n';

    const float f = 1.0f;
    //pi * f; // doesn't work

    cout << math::as<double>(pi) * f << '\n';

    cout << math::as<long double>(pi * pi) << '\n';
    cout << math::as<long double>(pi2) << '\n';

    cout << math::as<long double>(std::acos(math::minus_one)) << '\n';
    
    
}

>
>Paul
>
>PS I'd like to hear more views on this -
>previous review comments were quite different,
>being very cautious about an 'advanced' scheme like this.

It's not so "advanced" as it might appear at first sight. Maybe the
rigmarole above, together with the comments in the alternative
solution, is of some help to realize that the basic ideas are very
simple.

Genny.


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