detail/test/numeric_traits_test.cpp
Andrey Semashev 15d8f9a59c Actualized numeric_traits implementation.
1. Ported to Boost.TypeTraits primitives for type inspection and simple
   metaprogramming instead of local implementations. This effectively
   drops any workarounds for compilers without support for partial template
   specializations. Such compilers are long outdated.

2. Include more fine-grained headers from Boost.TypeTraits to optimize
   compilation times.

3. Made numeric_distance function constexpr.

4. Updated the test accordingly (i.e. replaced the use of now removed local
   components with Boost.TypeTraits). Also replaced lexical_cast with
   standard streams to further reduce dependencies.

None of these changes should affect functionality.
2017-09-20 02:01:51 +03:00

417 lines
15 KiB
C++

// (C) Copyright David Abrahams 2001.
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
// See http://www.boost.org for most recent version including documentation.
// Revision History
// 1 Apr 2001 Fixes for ICL; use BOOST_STATIC_CONSTANT
// 11 Feb 2001 Fixes for Borland (David Abrahams)
// 23 Jan 2001 Added test for wchar_t (David Abrahams)
// 23 Jan 2001 Now statically selecting a test for signed numbers to avoid
// warnings with fancy compilers. Added commentary and
// additional dumping of traits data for tested types (David
// Abrahams).
// 21 Jan 2001 Initial version (David Abrahams)
#include <boost/detail/numeric_traits.hpp>
#include <cassert>
#include <boost/type_traits/conditional.hpp>
#include <boost/type_traits/is_signed.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/static_assert.hpp>
#include <boost/cstdint.hpp>
#include <climits>
#include <typeinfo>
#include <iostream>
#include <sstream>
#include <string>
#ifndef BOOST_NO_LIMITS
#include <limits>
#endif
// =================================================================================
// template class complement_traits<Number> --
//
// statically computes the max and min for 1s and 2s-complement binary
// numbers. This helps on platforms without <limits> support. It also shows
// an example of a recursive template that works with MSVC!
//
template <unsigned size> struct complement; // forward
// The template complement, below, does all the real work, using "poor man's
// partial specialization". We need complement_traits_aux<> so that MSVC doesn't
// complain about undefined min/max as we're trying to recursively define them.
template <class Number, unsigned size>
struct complement_traits_aux
{
BOOST_STATIC_CONSTANT(Number, max = complement<size>::template traits<Number>::max);
BOOST_STATIC_CONSTANT(Number, min = complement<size>::template traits<Number>::min);
};
template <unsigned size>
struct complement
{
template <class Number>
struct traits
{
private:
// indirection through complement_traits_aux necessary to keep MSVC happy
typedef complement_traits_aux<Number, size - 1> prev;
public:
#if defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 0 && __GNUC_PATCHLEVEL__ == 2
// GCC 4.0.2 ICEs on these C-style casts
BOOST_STATIC_CONSTANT(Number, max =
Number((prev::max) << CHAR_BIT)
+ Number(UCHAR_MAX));
BOOST_STATIC_CONSTANT(Number, min = Number((prev::min) << CHAR_BIT));
#else
// Avoid left shifting negative integers, use multiplication instead
BOOST_STATIC_CONSTANT(Number, shift = 1u << CHAR_BIT);
BOOST_STATIC_CONSTANT(Number, max =
Number(Number(prev::max) * shift)
+ Number(UCHAR_MAX));
BOOST_STATIC_CONSTANT(Number, min = Number(Number(prev::min) * shift));
#endif
};
};
// Template class complement_base<> -- defines values for min and max for
// complement<1>, at the deepest level of recursion. Uses "poor man's partial
// specialization" again.
template <bool is_signed> struct complement_base;
template <> struct complement_base<false>
{
template <class Number>
struct values
{
BOOST_STATIC_CONSTANT(Number, min = 0);
BOOST_STATIC_CONSTANT(Number, max = UCHAR_MAX);
};
};
template <> struct complement_base<true>
{
template <class Number>
struct values
{
BOOST_STATIC_CONSTANT(Number, min = SCHAR_MIN);
BOOST_STATIC_CONSTANT(Number, max = SCHAR_MAX);
};
};
// Base specialization of complement, puts an end to the recursion.
template <>
struct complement<1>
{
template <class Number>
struct traits
{
BOOST_STATIC_CONSTANT(bool, is_signed = boost::is_signed<Number>::value);
BOOST_STATIC_CONSTANT(Number, min =
complement_base<is_signed>::template values<Number>::min);
BOOST_STATIC_CONSTANT(Number, max =
complement_base<is_signed>::template values<Number>::max);
};
};
// Now here's the "pretty" template you're intended to actually use.
// complement_traits<Number>::min, complement_traits<Number>::max are the
// minimum and maximum values of Number if Number is a built-in integer type.
template <class Number>
struct complement_traits
{
BOOST_STATIC_CONSTANT(Number, max = (complement_traits_aux<Number, sizeof(Number)>::max));
BOOST_STATIC_CONSTANT(Number, min = (complement_traits_aux<Number, sizeof(Number)>::min));
};
// =================================================================================
// Support for streaming various numeric types in exactly the format I want. I
// needed this in addition to all the assertions so that I could see exactly
// what was going on.
//
// Numbers go through a 2-stage conversion process (by default, though, no real
// conversion).
//
template <class T> struct stream_as {
typedef T t1;
typedef T t2;
};
// char types first get converted to unsigned char, then to unsigned.
template <> struct stream_as<char> {
typedef unsigned char t1;
typedef unsigned t2;
};
template <> struct stream_as<unsigned char> {
typedef unsigned char t1; typedef unsigned t2;
};
template <> struct stream_as<signed char> {
typedef unsigned char t1; typedef unsigned t2;
};
#if defined(BOOST_MSVC_STD_ITERATOR) // No intmax streaming built-in
// With this library implementation, __int64 and __uint64 get streamed as strings
template <> struct stream_as<boost::uintmax_t> {
typedef std::string t1;
typedef std::string t2;
};
template <> struct stream_as<boost::intmax_t> {
typedef std::string t1;
typedef std::string t2;
};
#endif
// Standard promotion process for streaming
template <class T> struct promote
{
static typename stream_as<T>::t1 from(T x) {
typedef typename stream_as<T>::t1 t1;
return t1(x);
}
};
#if defined(BOOST_MSVC_STD_ITERATOR) // No intmax streaming built-in
// On this platform, stream them as long/unsigned long if they fit.
// Otherwise, write a string.
template <> struct promote<boost::uintmax_t> {
std::string static from(const boost::uintmax_t x) {
if (x > ULONG_MAX)
return std::string("large unsigned value");
else {
std::ostringstream strm;
strm << (unsigned long)x;
return strm.str();
}
}
};
template <> struct promote<boost::intmax_t> {
std::string static from(const boost::intmax_t x) {
if (x > boost::intmax_t(ULONG_MAX))
return std::string("large positive signed value");
else if (x >= 0) {
std::ostringstream strm;
strm << (unsigned long)x;
return strm.str();
}
if (x < boost::intmax_t(LONG_MIN))
return std::string("large negative signed value");
else {
std::ostringstream strm;
strm << (long)x;
return strm.str();
}
}
};
#endif
// This is the function which converts types to the form I want to stream them in.
template <class T>
typename stream_as<T>::t2 stream_number(T x)
{
return promote<T>::from(x);
}
// =================================================================================
//
// Tests for built-in signed and unsigned types
//
// Tag types for selecting tests
struct unsigned_tag {};
struct signed_tag {};
// Tests for unsigned numbers. The extra default Number parameter works around
// an MSVC bug.
template <class Number>
void test_aux(unsigned_tag, Number*)
{
typedef typename boost::detail::numeric_traits<Number>::difference_type difference_type;
BOOST_STATIC_ASSERT(!boost::is_signed<Number>::value);
BOOST_STATIC_ASSERT(
(sizeof(Number) < sizeof(boost::intmax_t))
| (boost::is_same<difference_type, boost::intmax_t>::value));
#if defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 0 && __GNUC_PATCHLEVEL__ == 2
// GCC 4.0.2 ICEs on this C-style cases
BOOST_STATIC_ASSERT((complement_traits<Number>::max) > Number(0));
BOOST_STATIC_ASSERT((complement_traits<Number>::min) == Number(0));
#else
// Force casting to Number here to work around the fact that it's an enum on MSVC
BOOST_STATIC_ASSERT(Number(complement_traits<Number>::max) > Number(0));
BOOST_STATIC_ASSERT(Number(complement_traits<Number>::min) == Number(0));
#endif
const Number max = complement_traits<Number>::max;
const Number min = complement_traits<Number>::min;
const Number test_max = (sizeof(Number) < sizeof(boost::intmax_t))
? max
: max / 2 - 1;
std::cout << std::hex << "(unsigned) min = " << stream_number(min) << ", max = "
<< stream_number(max) << "..." << std::flush;
std::cout << "difference_type = " << typeid(difference_type).name() << "..."
<< std::flush;
difference_type d1 = boost::detail::numeric_distance(Number(0), test_max);
difference_type d2 = boost::detail::numeric_distance(test_max, Number(0));
std::cout << "0->" << stream_number(test_max) << "==" << std::dec << stream_number(d1) << "; "
<< std::hex << stream_number(test_max) << "->0==" << std::dec << stream_number(d2) << "..." << std::flush;
assert(d1 == difference_type(test_max));
assert(d2 == -difference_type(test_max));
}
// Tests for signed numbers. The extra default Number parameter works around an
// MSVC bug.
struct out_of_range_tag {};
struct in_range_tag {};
// This test morsel gets executed for numbers whose difference will always be
// representable in intmax_t
template <class Number>
void signed_test(in_range_tag, Number*)
{
BOOST_STATIC_ASSERT(boost::is_signed<Number>::value);
typedef typename boost::detail::numeric_traits<Number>::difference_type difference_type;
const Number max = complement_traits<Number>::max;
const Number min = complement_traits<Number>::min;
difference_type d1 = boost::detail::numeric_distance(min, max);
difference_type d2 = boost::detail::numeric_distance(max, min);
std::cout << stream_number(min) << "->" << stream_number(max) << "==";
std::cout << std::dec << stream_number(d1) << "; ";
std::cout << std::hex << stream_number(max) << "->" << stream_number(min)
<< "==" << std::dec << stream_number(d2) << "..." << std::flush;
assert(d1 == difference_type(max) - difference_type(min));
assert(d2 == difference_type(min) - difference_type(max));
}
// This test morsel gets executed for numbers whose difference may exceed the
// capacity of intmax_t.
template <class Number>
void signed_test(out_of_range_tag, Number*)
{
BOOST_STATIC_ASSERT(boost::is_signed<Number>::value);
typedef typename boost::detail::numeric_traits<Number>::difference_type difference_type;
const Number max = complement_traits<Number>::max;
const Number min = complement_traits<Number>::min;
difference_type min_distance = complement_traits<difference_type>::min;
difference_type max_distance = complement_traits<difference_type>::max;
const Number n1 = Number(min + max_distance);
const Number n2 = Number(max + min_distance);
difference_type d1 = boost::detail::numeric_distance(min, n1);
difference_type d2 = boost::detail::numeric_distance(max, n2);
std::cout << stream_number(min) << "->" << stream_number(n1) << "==";
std::cout << std::dec << stream_number(d1) << "; ";
std::cout << std::hex << stream_number(max) << "->" << stream_number(n2)
<< "==" << std::dec << stream_number(d2) << "..." << std::flush;
assert(d1 == max_distance);
assert(d2 == min_distance);
}
template <class Number>
void test_aux(signed_tag, Number*)
{
typedef typename boost::detail::numeric_traits<Number>::difference_type difference_type;
BOOST_STATIC_ASSERT(boost::is_signed<Number>::value);
BOOST_STATIC_ASSERT(
(sizeof(Number) < sizeof(boost::intmax_t))
| (boost::is_same<difference_type, Number>::value));
#if defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 0 && __GNUC_PATCHLEVEL__ == 2
// GCC 4.0.2 ICEs on this cast
BOOST_STATIC_ASSERT((complement_traits<Number>::max) > Number(0));
BOOST_STATIC_ASSERT((complement_traits<Number>::min) < Number(0));
#else
// Force casting to Number here to work around the fact that it's an enum on MSVC
BOOST_STATIC_ASSERT(Number(complement_traits<Number>::max) > Number(0));
BOOST_STATIC_ASSERT(Number(complement_traits<Number>::min) < Number(0));
#endif
const Number max = complement_traits<Number>::max;
const Number min = complement_traits<Number>::min;
std::cout << std::hex << "min = " << stream_number(min) << ", max = "
<< stream_number(max) << "..." << std::flush;
std::cout << "difference_type = " << typeid(difference_type).name() << "..."
<< std::flush;
typedef typename boost::conditional<
(sizeof(Number) < sizeof(boost::intmax_t)),
in_range_tag,
out_of_range_tag
>::type range_tag;
signed_test<Number>(range_tag(), 0);
}
// Test for all numbers. The extra default Number parameter works around an MSVC
// bug.
template <class Number>
void test(Number* = 0)
{
std::cout << "testing " << typeid(Number).name() << ":\n"
#ifndef BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS
<< "is_signed: " << (std::numeric_limits<Number>::is_signed ? "true\n" : "false\n")
<< "is_bounded: " << (std::numeric_limits<Number>::is_bounded ? "true\n" : "false\n")
<< "digits: " << std::numeric_limits<Number>::digits << "\n"
#endif
<< "..." << std::flush;
// factoring out difference_type for the assert below confused Borland :(
typedef boost::is_signed<
#if !defined(BOOST_MSVC) || BOOST_MSVC > 1300
typename
#endif
boost::detail::numeric_traits<Number>::difference_type
> is_signed;
BOOST_STATIC_ASSERT(is_signed::value);
typedef typename boost::conditional<
boost::is_signed<Number>::value,
signed_tag,
unsigned_tag
>::type signedness;
test_aux<Number>(signedness(), 0);
std::cout << "passed" << std::endl;
}
int main()
{
test<char>();
test<unsigned char>();
test<signed char>();
test<wchar_t>();
test<short>();
test<unsigned short>();
test<int>();
test<unsigned int>();
test<long>();
test<unsigned long>();
#if defined(BOOST_HAS_LONG_LONG) && !defined(BOOST_NO_INTEGRAL_INT64_T)
test< ::boost::long_long_type>();
test< ::boost::ulong_long_type>();
#elif defined(BOOST_MSVC)
// The problem of not having compile-time static class constants other than
// enums prevents this from working, since values get truncated.
// test<boost::uintmax_t>();
// test<boost::intmax_t>();
#endif
return 0;
}