[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master 5e36b0a: Add bourn_cast, with unit tests, to
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master 5e36b0a: Add bourn_cast, with unit tests, to replace boost::numeric_cast |
Date: |
Sat, 18 Mar 2017 09:35:57 -0400 (EDT) |
branch: master
commit 5e36b0a6bc9fed0788b6f74a9896387c8a729cb4
Author: Gregory W. Chicares <address@hidden>
Commit: Gregory W. Chicares <address@hidden>
Add bourn_cast, with unit tests, to replace boost::numeric_cast
---
Makefile.am | 8 ++
bourn_cast.hpp | 97 +++++++++++++++
bourn_cast_test.cpp | 344 ++++++++++++++++++++++++++++++++++++++++++++++++++++
objects.make | 6 +
4 files changed, 455 insertions(+)
diff --git a/Makefile.am b/Makefile.am
index d818266..c43865d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -89,6 +89,7 @@ TESTS = \
test_any_member \
test_assert_lmi \
test_authenticity \
+ test_bourn_cast \
test_cache_file_reads \
test_calendar_date \
test_callback \
@@ -579,6 +580,12 @@ test_authenticity_CXXFLAGS = $(AM_CXXFLAGS)
test_authenticity_LDADD = \
$(BOOST_LIBS)
+test_bourn_cast_SOURCES = \
+ $(common_test_objects) \
+ bourn_cast_test.cpp \
+ timer.cpp
+test_bourn_cast_CXXFLAGS = $(AM_CXXFLAGS)
+
test_cache_file_reads_SOURCES = \
$(common_test_objects) \
cache_file_reads_test.cpp \
@@ -1084,6 +1091,7 @@ noinst_HEADERS = \
assert_lmi.hpp \
authenticity.hpp \
basic_values.hpp \
+ bourn_cast.hpp \
cache_file_reads.hpp \
calendar_date.hpp \
callback.hpp \
diff --git a/bourn_cast.hpp b/bourn_cast.hpp
new file mode 100644
index 0000000..87b61bb
--- /dev/null
+++ b/bourn_cast.hpp
@@ -0,0 +1,97 @@
+// Numeric stinted cast, across whose bourn no value is returned.
+//
+// Copyright (C) 2017 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#ifndef bourn_cast_hpp
+#define bourn_cast_hpp
+
+#include "config.hpp"
+
+#include <limits>
+#include <stdexcept>
+
+/// Numeric stinted cast, across whose bourn no value is returned.
+///
+/// Perform a static_cast between numeric types, but throw if the
+/// value is out of range. For example:
+/// bourn_cast<unsigned int>( 1); // Returns 1U.
+/// bourn_cast<unsigned int>(-1); // Throws.
+///
+/// Motivation: To convert between integral types that may differ in
+/// size and signedness, iff the value is between the maximum and
+/// minimum values permitted for the target (From) type. Because of
+/// the properties of integers, conversion between integral types
+/// either preserves the notional value, or throws.
+///
+/// Both From and To must be types for which std::numeric_limits is
+/// specialized. Use with floating-point types is neither forbidden
+/// nor encouraged. Integral-to-floating conversion is highly unlikely
+/// to exceed bounds, but may lose precision. Floating-to-integral
+/// conversion is extremely unlikely to preserve value, so a rounding
+/// facility is generally preferable. No special attention is given to
+/// exotic values such as infinities, NaNs, or negative zero. For now,
+/// bourn_cast<>() is intended as a simple replacement for the heavier
+/// "improved" boost::numeric_cast<>(), but in the future floating-
+/// point types may be forbidden.
+///
+/// This is a derived work based on Kevlin Henney's numeric_cast,
+/// which is presented on his site without any copyright notice:
+///
http://www.two-sdg.demon.co.uk/curbralan/code/numeric_cast/numeric_cast.hpp
+/// and also as part of boost, with the following notice:
+/// (C) Copyright Kevlin Henney and Dave Abrahams 1999.
+/// Distributed under the Boost Software License, Version 1.0.
+/// According to
+/// http://www.gnu.org/philosophy/license-list.html
+/// "This is a simple, permissive non-copyleft free software
+/// license, compatible with the GNU GPL."
+///
+/// Rewritten by Gregory W. Chicares in 2017. Any defect here should
+/// not reflect on Kevlin Henney's reputation.
+///
+/// Also see:
+///
https://groups.google.com/forum/#!original/comp.std.c++/WHu6gUiwXkU/ZyV_ejRrXFYJ
+/// which may be an independent redesign.
+
+template<typename To, typename From>
+To bourn_cast(From from)
+{
+# if defined __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wsign-compare"
+# endif // defined __GNUC__
+ using to_traits = std::numeric_limits<To>;
+ using from_traits = std::numeric_limits<From>;
+ static_assert( to_traits::is_specialized, "");
+ static_assert(from_traits::is_specialized, "");
+
+ if(! to_traits::is_signed && from < 0)
+ throw std::runtime_error("Cast would convert negative to unsigned.");
+ if(from_traits::is_signed && from < to_traits::lowest())
+ throw std::runtime_error("Cast would transgress lower limit.");
+ if(to_traits::max() < from)
+ throw std::runtime_error("Cast would transgress upper limit.");
+ return static_cast<To>(from);
+# if defined __GNUC__
+# pragma GCC diagnostic pop
+# endif // defined __GNUC__
+}
+
+#endif // bourn_cast_hpp
+
diff --git a/bourn_cast_test.cpp b/bourn_cast_test.cpp
new file mode 100644
index 0000000..1732e98
--- /dev/null
+++ b/bourn_cast_test.cpp
@@ -0,0 +1,344 @@
+// Numeric stinted cast, across whose bourn no value is returned--unit test.
+//
+// Copyright (C) 2017 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#include "pchfile.hpp"
+
+#include "bourn_cast.hpp"
+
+#include "miscellany.hpp" // stifle_warning_for_unused_variable()
+#include "test_tools.hpp"
+#include "timer.hpp"
+
+#include <climits> // LLONG_MIN, SCHAR_MIN, etc.
+#include <type_traits> // std::conditional
+
+/// Test trivial casts between identical types.
+
+template<typename T>
+void test_same(char const* file, int line)
+{
+ using limits = std::numeric_limits<T>;
+ T upper = limits::max();
+ T lower = limits::lowest();
+ INVOKE_BOOST_TEST_EQUAL(upper, bourn_cast<T>(upper), file, line);
+ INVOKE_BOOST_TEST_EQUAL(T( 1), bourn_cast<T>(T( 1)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(T( 0), bourn_cast<T>(T( 0)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(lower, bourn_cast<T>(lower), file, line);
+ if(limits::is_signed)
+ {
+ INVOKE_BOOST_TEST_EQUAL(T(-1), bourn_cast<T>(T(-1)), file, line);
+ }
+}
+
+/// Test casts involving two possibly different signednesses.
+
+template<bool SignedFrom, bool SignedTo>
+void test_signednesses(char const* file, int line)
+{
+ using CS = signed char;
+ using IS = signed int;
+ using LS = signed long long int;
+
+ using CU = unsigned char;
+ using IU = unsigned int;
+ using LU = unsigned long long int;
+
+ using CFrom = typename std::conditional<SignedFrom, CS, CU>::type;
+ using IFrom = typename std::conditional<SignedFrom, IS, IU>::type;
+ using LFrom = typename std::conditional<SignedFrom, LS, LU>::type;
+
+ using CTo = typename std::conditional<SignedTo , CS, CU>::type;
+ using ITo = typename std::conditional<SignedTo , IS, IU>::type;
+ using LTo = typename std::conditional<SignedTo , LS, LU>::type;
+
+ // For any pair of corresponding signed and unsigned integral
+ // types, the maximum signed value is interconvertible. Thus,
+ // corresponding elements of these two triplets have the same
+ // value--and indeed the same bit representation--but different
+ // types.
+
+ CFrom CFrom_max = SCHAR_MAX;
+ IFrom IFrom_max = INT_MAX;
+ LFrom LFrom_max = LLONG_MAX;
+
+ CTo CTo_max = SCHAR_MAX;
+ ITo ITo_max = INT_MAX;
+ LTo LTo_max = LLONG_MAX;
+
+ // SCHAR_MAX must be at least 127, so 99 must be representable.
+
+ // Both char.
+ INVOKE_BOOST_TEST_EQUAL(CTo( 0), bourn_cast<CTo>(CFrom( 0)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(CTo( 1), bourn_cast<CTo>(CFrom( 1)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(CTo(99), bourn_cast<CTo>(CFrom(99)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(CTo_max, bourn_cast<CTo>(CFrom_max), file, line);
+
+ // Both int.
+ INVOKE_BOOST_TEST_EQUAL(ITo( 0), bourn_cast<ITo>(IFrom( 0)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(ITo( 1), bourn_cast<ITo>(IFrom( 1)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(ITo(99), bourn_cast<ITo>(IFrom(99)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(ITo_max, bourn_cast<ITo>(IFrom_max), file, line);
+
+ // Both long long.
+ INVOKE_BOOST_TEST_EQUAL(LTo( 0), bourn_cast<LTo>(LFrom( 0)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(LTo( 1), bourn_cast<LTo>(LFrom( 1)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(LTo(99), bourn_cast<LTo>(LFrom(99)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(LTo_max, bourn_cast<LTo>(LFrom_max), file, line);
+
+ // To wider than From.
+ INVOKE_BOOST_TEST_EQUAL(CTo_max, bourn_cast<ITo>(CFrom_max), file, line);
+ INVOKE_BOOST_TEST_EQUAL(CTo_max, bourn_cast<ITo>(CFrom_max), file, line);
+ INVOKE_BOOST_TEST_EQUAL(ITo_max, bourn_cast<LTo>(IFrom_max), file, line);
+
+ // From wider than To.
+ INVOKE_BOOST_TEST_EQUAL(CTo( 0), bourn_cast<CTo>(IFrom( 0)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(CTo( 1), bourn_cast<CTo>(LFrom( 1)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(CTo(99), bourn_cast<CTo>(LFrom(99)), file, line);
+
+ if(!SignedFrom || !SignedTo) return;
+
+ CFrom CFrom_min = SCHAR_MIN;
+ IFrom IFrom_min = INT_MIN;
+ LFrom LFrom_min = LLONG_MIN;
+
+ CTo CTo_min = SCHAR_MIN;
+ ITo ITo_min = INT_MIN;
+ LTo LTo_min = LLONG_MIN;
+
+ // SCHAR_MIN must be <= -127, so -9 must be representable.
+
+ // Both char.
+ INVOKE_BOOST_TEST_EQUAL(CTo(-1), bourn_cast<CTo>(CFrom(-1)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(CTo(-9), bourn_cast<CTo>(CFrom(-9)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(CTo_min, bourn_cast<CTo>(CFrom_min), file, line);
+
+ // Both int.
+ INVOKE_BOOST_TEST_EQUAL(ITo(-1), bourn_cast<ITo>(IFrom(-1)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(ITo(-9), bourn_cast<ITo>(IFrom(-9)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(ITo_min, bourn_cast<ITo>(IFrom_min), file, line);
+
+ // Both long long.
+ INVOKE_BOOST_TEST_EQUAL(LTo(-1), bourn_cast<LTo>(LFrom(-1)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(LTo(-9), bourn_cast<LTo>(LFrom(-9)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(LTo_min, bourn_cast<LTo>(LFrom_min), file, line);
+
+ // To wider than From.
+ INVOKE_BOOST_TEST_EQUAL(CTo_min, bourn_cast<ITo>(CFrom_min), file, line);
+ INVOKE_BOOST_TEST_EQUAL(CTo_min, bourn_cast<ITo>(CFrom_min), file, line);
+ INVOKE_BOOST_TEST_EQUAL(ITo_min, bourn_cast<LTo>(IFrom_min), file, line);
+
+ // From wider than To.
+ INVOKE_BOOST_TEST_EQUAL(CTo(-9), bourn_cast<CTo>(IFrom(-9)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(CTo(-9), bourn_cast<CTo>(LFrom(-9)), file, line);
+ INVOKE_BOOST_TEST_EQUAL(ITo(-9), bourn_cast<ITo>(LFrom(-9)), file, line);
+}
+
+/// Directly assign unsigned to signed a million times, for timing.
+///
+/// One million is guaranteed to be within range, because LONG_MAX
+/// can be no lower than the eighth Mersenne prime, and ULONG_MAX
+/// must be even greater.
+
+void mete_direct()
+{
+ volatile signed long int z = 0;
+ for(unsigned long int j = 0; j < 1000000; ++j)
+ {
+ z = j;
+ }
+ stifle_warning_for_unused_variable(z);
+}
+
+/// Convert signed to unsigned a million times, for timing.
+
+void mete_signed_to_unsigned()
+{
+ volatile unsigned long int z = 0;
+ for(signed long int j = 0; j < 1000000; ++j)
+ {
+ z = bourn_cast<unsigned long int>(j);
+ }
+ stifle_warning_for_unused_variable(z);
+}
+
+/// Convert unsigned to signed a million times, for timing.
+
+void mete_unsigned_to_signed()
+{
+ volatile signed long int z = 0;
+ for(unsigned long int j = 0; j < 1000000; ++j)
+ {
+ z = bourn_cast<signed long int>(j);
+ }
+ stifle_warning_for_unused_variable(z);
+}
+
+void assay_speed()
+{
+ std::cout
+ << "\n Speed tests..."
+ << "\n direct: " << TimeAnAliquot(mete_direct)
+ << "\n S to U: " << TimeAnAliquot(mete_signed_to_unsigned)
+ << "\n U to S: " << TimeAnAliquot(mete_unsigned_to_signed)
+ << std::endl
+ ;
+}
+
+int test_main(int, char*[])
+{
+ // Motivating case. This test fails: -1 really isn't less than 1U.
+ // (The test is suppressed to avoid a compiler warning.)
+// BOOST_TEST(-1 < 1U);
+ // This test succeeds: -1 is less than 1, as expected.
+ BOOST_TEST(-1 < bourn_cast<int>(1U));
+ // This test throws: instead of converting a negative value to
+ // unsigned, bourn_cast throws an exception.
+// BOOST_TEST(bourn_cast<unsigned int>(-1) < 1U);
+
+ // Trivially cast to same type.
+
+ test_same<bool> (__FILE__, __LINE__);
+ test_same<char> (__FILE__, __LINE__);
+ test_same<signed char> (__FILE__, __LINE__);
+ test_same<unsigned char> (__FILE__, __LINE__);
+ test_same<char16_t> (__FILE__, __LINE__);
+ test_same<char32_t> (__FILE__, __LINE__);
+ test_same<wchar_t> (__FILE__, __LINE__);
+ test_same<short int> (__FILE__, __LINE__);
+ test_same<int> (__FILE__, __LINE__);
+ test_same<long int> (__FILE__, __LINE__);
+ test_same<long long int> (__FILE__, __LINE__);
+ test_same<unsigned short int> (__FILE__, __LINE__);
+ test_same<unsigned int> (__FILE__, __LINE__);
+ test_same<unsigned long int> (__FILE__, __LINE__);
+ test_same<unsigned long long int>(__FILE__, __LINE__);
+ test_same<float> (__FILE__, __LINE__);
+ test_same<double> (__FILE__, __LINE__);
+ test_same<long double> (__FILE__, __LINE__);
+
+ // Cast between bool and int. C++11 [18.3.2.7/3] specifies that
+ // std::numeric_limits<bool>is_signed is false, so the types
+ // {bool, signed char} must have opposite signedness and different
+ // [lowest(), max()] ranges. Therefore, the tests in this block
+ // are guaranteed to cover such diversity, even on a machine where
+ // unsigned char and unsigned long long int are synonyms.
+
+ BOOST_TEST_EQUAL(true , bourn_cast<bool>((signed char)(1)));
+ BOOST_TEST_EQUAL(false, bourn_cast<bool>((signed char)(0)));
+
+ BOOST_TEST_THROW
+ (bourn_cast<bool>((signed char)(2))
+ ,std::runtime_error
+ ,"Cast would transgress upper limit."
+ );
+
+ BOOST_TEST_THROW
+ (bourn_cast<bool>((signed char)(-1))
+ ,std::runtime_error
+ ,"Cast would convert negative to unsigned."
+ );
+
+ // Cast from signed to unsigned.
+
+ test_signednesses<true,false>(__FILE__, __LINE__);
+
+ // Cast from unsigned to signed.
+
+ test_signednesses<false,true>(__FILE__, __LINE__);
+
+ // Cast from signed to signed.
+
+ test_signednesses<true,true>(__FILE__, __LINE__);
+
+ // Cast from unsigned to unsigned.
+
+ test_signednesses<false,false>(__FILE__, __LINE__);
+
+ // Attempt forbidden conversion from negative to unsigned.
+
+ BOOST_TEST_THROW
+ (bourn_cast<unsigned char>(std::numeric_limits<signed char>::lowest())
+ ,std::runtime_error
+ ,"Cast would convert negative to unsigned."
+ );
+
+ BOOST_TEST_THROW
+ (bourn_cast<unsigned int >(std::numeric_limits<signed int >::lowest())
+ ,std::runtime_error
+ ,"Cast would convert negative to unsigned."
+ );
+
+ // Still forbidden even if unsigned type is wider.
+ BOOST_TEST_THROW
+ (bourn_cast<unsigned long>(std::numeric_limits<signed char>::lowest())
+ ,std::runtime_error
+ ,"Cast would convert negative to unsigned."
+ );
+
+ // Still forbidden even if value is only "slightly" negative.
+ BOOST_TEST_THROW
+ (bourn_cast<unsigned long>(-1)
+ ,std::runtime_error
+ ,"Cast would convert negative to unsigned."
+ );
+
+ // Transgress lower limit. It is not possible to write a unit test
+ // that is guaranteed to throw this particular exception, because
+ // the present bourn_cast<>() implementation tests first for
+ // attempted conversion of a negative value to an unsigned type.
+
+#if LLONG_MIN < SCHAR_MIN
+ BOOST_TEST_THROW
+ (bourn_cast<signed char>(LLONG_MIN)
+ ,std::runtime_error
+ ,"Cast would transgress lower limit."
+ );
+#endif // LLONG_MIN < SCHAR_MIN
+
+ // Transgress upper limit.
+
+#if UCHAR_MAX < ULLONG_MAX
+ BOOST_TEST_THROW
+ (bourn_cast<unsigned char>(ULLONG_MAX)
+ ,std::runtime_error
+ ,"Cast would transgress upper limit."
+ );
+#endif // UCHAR_MAX < ULLONG_MAX
+
+ BOOST_TEST_THROW
+ (bourn_cast<signed char>(std::numeric_limits<unsigned char>::max())
+ ,std::runtime_error
+ ,"Cast would transgress upper limit."
+ );
+
+ BOOST_TEST_THROW
+ (bourn_cast<signed int >(std::numeric_limits<unsigned int >::max())
+ ,std::runtime_error
+ ,"Cast would transgress upper limit."
+ );
+
+ // Time representative casts.
+
+ assay_speed();
+
+ return EXIT_SUCCESS;
+}
+
diff --git a/objects.make b/objects.make
index 9eaa9ff..6e2105e 100644
--- a/objects.make
+++ b/objects.make
@@ -400,6 +400,7 @@ unit_test_targets := \
any_member_test \
assert_lmi_test \
authenticity_test \
+ bourn_cast_test \
cache_file_reads_test \
calendar_date_test \
callback_test \
@@ -517,6 +518,11 @@ authenticity_test$(EXEEXT): \
system_command.o \
system_command_non_wx.o \
+bourn_cast_test$(EXEEXT): \
+ $(common_test_objects) \
+ bourn_cast_test.o \
+ timer.o \
+
cache_file_reads_test$(EXEEXT): \
$(boost_filesystem_objects) \
$(common_test_objects) \
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [lmi-commits] [lmi] master 5e36b0a: Add bourn_cast, with unit tests, to replace boost::numeric_cast,
Greg Chicares <=