Range-aware version of min and max

I am often frustrated by the inability of std::min and std::max to deal with data types of different ranges. For example: boost::uint64_t big = 9876543210ULL; boost::uint32_t small = 12345; boost::uint32_t smallest = std::min(big, small); will generate a compiler warning about possible loss of data due to converting from boost::uint64_t to boost::uint32_t. Obviously such a scenario is impossible since by definition the result can be no larger than the maximum value of a uint32. Previously there was a discussion on the list about adding operators such as is_addable, is_multipliable, is_less_comparable, etc. Using such a class, one could re-write min and max as follows (I'm sure someone will come up with a problem with this, or a better implementation, but anyway the idea should be clear): namespace boost { template<class A, class B> struct min_result { typedef mpl::if_c<(integer_traits<A>::const_max < integer_traits<B>::const_max), A, B>::type type; }; template<class A, class B> struct max_result { typedef mpl::if_c<(integer_traits<A>::const_min > integer_traits<B>::const_min), A, B>::type type; }; template<class A, class B> min_result<A,B>::type min(const A& a, const B& b) { return static_cast<min_result<A,B>::type>((a < b) ? a : b); } template<class A, class B> max_result<A,B>::type max(const A& a, const B& b) { return static_cast<max_result<A,B>::type>((a > b) ? a : b); } I'm sure there's some implementation details I haven't considered, but is there any fundamental reason why an approach like this would be flawed or undesirable? Zach

namespace boost {
template<class A, class B> struct min_result template<class A, class B> struct max_result
I'm sure there's some implementation details I haven't considered, but is there any fundamental reason why an approach like this would be flawed or undesirable?
I think the idea of the return types is more general than just for min_result and max_result. I would prefer to see a type generator for type promotion and demotion. This is generally useful in other scenarios such as matrix return types etc. May I suggest promote_type<A,B> and demote_type<A,B> as the names for the appropriate type generators?
Zach
Neil Groves

Zachary Turner wrote:
I am often frustrated by the inability of std::min and std::max to deal with data types of different ranges. For example:
boost::uint64_t big = 9876543210ULL; boost::uint32_t small = 12345;
boost::uint32_t smallest = std::min(big, small);
will generate a compiler warning about possible loss of data due to converting from boost::uint64_t to boost::uint32_t.
It doesn't seem to compile: http://codepad.org/IiFba8MF I guess you mean: boost::uint32_t smallest = std::min<boost::uint32_t>(big, small);
Previously there was a discussion on the list about adding operators such as is_addable, is_multipliable, is_less_comparable, etc. Using such a class, one could re-write min and max as follows (...):
namespace boost {
template<class A, class B> struct min_result { typedef mpl::if_c<(integer_traits<A>::const_max < integer_traits<B>::const_max), A, B>::type type; };
template<class A, class B> struct max_result { typedef mpl::if_c<(integer_traits<A>::const_min > integer_traits<B>::const_min), A, B>::type type; };
template<class A, class B> min_result<A,B>::type min(const A& a, const B& b) { return static_cast<min_result<A,B>::type>((a < b) ? a : b); }
template<class A, class B> max_result<A,B>::type max(const A& a, const B& b) { return static_cast<max_result<A,B>::type>((a > b) ? a : b); }
Excuse me, where do you use is_addable, is_multipliable, is_less_comparable?
I'm sure there's some implementation details I haven't considered, but is there any fundamental reason why an approach like this would be flawed or undesirable?
I think your suggestion is quite reasonable, but I see two drawbacks: * It only seems to support numeric types. Why not support the minimum of two datetime objects, or the minimun of two std::string objects? * It returns by-value. In general, I prefer min/max to return a reference to the object itself, instead of a copy. Like std::min and std::max (C++03). Both drawbacks could be avoided by renaming your function, for example to numeric_min_value. HTH, Niels -- Niels Dekker http://www.xs4all.nl/~nd/dekkerware Scientific programmer at LKEB, Leiden University Medical Center

On Mon, Jan 25, 2010 at 10:19 AM, Niels Dekker - address until 2010-10-10 < niels_address_until_2010-10-10@xs4all.nl> wrote:
Zachary Turner wrote:
I am often frustrated by the inability of std::min and std::max to deal with data types of different ranges. For example:
boost::uint64_t big = 9876543210ULL; boost::uint32_t small = 12345;
boost::uint32_t smallest = std::min(big, small);
will generate a compiler warning about possible loss of data due to converting from boost::uint64_t to boost::uint32_t.
It doesn't seem to compile: http://codepad.org/IiFba8MF I guess you mean:
boost::uint32_t smallest = std::min<boost::uint32_t>(big, small);
Previously there was a discussion on the list about adding operators
such as is_addable, is_multipliable, is_less_comparable, etc. Using such a class, one could re-write min and max as follows (...):
namespace boost {
template<class A, class B> struct min_result { typedef mpl::if_c<(integer_traits<A>::const_max < integer_traits<B>::const_max), A, B>::type type; };
template<class A, class B> struct max_result { typedef mpl::if_c<(integer_traits<A>::const_min > integer_traits<B>::const_min), A, B>::type type; };
template<class A, class B> min_result<A,B>::type min(const A& a, const B& b) { return static_cast<min_result<A,B>::type>((a < b) ? a : b); }
template<class A, class B> max_result<A,B>::type max(const A& a, const B& b) { return static_cast<max_result<A,B>::type>((a > b) ? a : b); }
Excuse me, where do you use is_addable, is_multipliable, is_less_comparable?
I don't use is_addable, is_multipliable, but is_less_comparable should be needed in order to determine if min is even a valid operation, and is_greater_comparable would be needed to determine if max is a valid operation, right? I only mentioned the others because the implementations are all equivalent.
I'm sure there's some implementation details I haven't considered,
but is there any fundamental reason why an approach like this would be flawed or undesirable?
I think your suggestion is quite reasonable, but I see two drawbacks:
* It only seems to support numeric types. Why not support the minimum of two datetime objects, or the minimun of two std::string objects?
Seems reasonable, but how would it select which type is to be returned? My original idea was that it would simply return whichever type had a smaller max (or larger min, depending on the operation). At first glance it appears more difficult to deal with this problem in a completely generic fashion when we bring in arbitrary types.
* It returns by-value. In general, I prefer min/max to return a reference to the object itself, instead of a copy. Like std::min and std::max (C++03).
Both drawbacks could be avoided by renaming your function, for example to numeric_min_value.
I like that idea. Although we might as well still return by reference. Are there any potential problems with using sizeof() to determine which type to return? Is there a better method that is more aware of the actual ranges supported by the types? Zach

Are there any potential problems with using sizeof() to determine which type to return? Is there a better method that is more aware of the actual ranges supported by the types?
sizeof() is insufficient since we need to know how to promote between for example, int and unsigned int. There is also the possibility that you might want to promote from int to double, but whether to allow this becomes questionable as one reaches the magnitude that suffers loss of precision. I have an implementation for the common arithmetic types that I would be prepared to submit. In my experience of using arithmetic type promotion and demotion it is often desirable to allow customisation of the promotion policy through a template parameter while providing a default policy that always preserves accuracy. Regards, Neil Groves

I'm actually glad we agree here :) I assume your implementation of numeric_min and numeric_max can take two arguments of differing types, and only does the promotion / demotion on the return? Unless you're planning to submit in the immediate future, could you post the implementation in this thread (assuming it's of reasonable length) so I can take a look? Zach On Wed, Jan 27, 2010 at 2:28 AM, Neil Groves <neil@grovescomputing.com>wrote:
In my experience of using arithmetic type promotion and demotion it is often desirable to allow customisation of the promotion policy through a template parameter while providing a default policy that always preserves accuracy.

At Tue, 26 Jan 2010 16:36:32 -0600, Zachary Turner wrote:
is_less_comparable should be needed in order to determine if min is even a valid operation, and is_greater_comparable would be needed to determine if max is a valid operation, right?
is_less_comparable would suffice for both unless you have a very unusual definition of min and max. -- Dave Abrahams Meet me at BoostCon: http://www.boostcon.com BoostPro Computing http://www.boostpro.com
participants (4)
-
David Abrahams
-
Neil Groves
-
Niels Dekker - address until 2010-10-10
-
Zachary Turner