Boost logo

Boost-Commit :

Subject: [Boost-commit] svn:boost r85484 - in trunk: boost libs/rational libs/rational/test
From: dwalker07_at_[hidden]
Date: 2013-08-27 06:35:26


Author: dlwalker
Date: 2013-08-27 06:35:26 EDT (Tue, 27 Aug 2013)
New Revision: 85484
URL: http://svn.boost.org/trac/boost/changeset/85484

Log:
Added cross-instantiation constructor template, refs #9018.

Text files modified:
   trunk/boost/rational.hpp | 31 +++++++++++++++++++++
   trunk/libs/rational/rational.html | 59 +++++++++++++++++++++++++++------------
   trunk/libs/rational/test/rational_test.cpp | 40 +++++++++++++++++++++++++++
   3 files changed, 112 insertions(+), 18 deletions(-)

Modified: trunk/boost/rational.hpp
==============================================================================
--- trunk/boost/rational.hpp Tue Aug 27 03:51:27 2013 (r85483)
+++ trunk/boost/rational.hpp 2013-08-27 06:35:26 EDT (Tue, 27 Aug 2013) (r85484)
@@ -21,6 +21,9 @@
 // Nickolay Mladenov, for the implementation of operator+=
 
 // Revision History
+// 27 Aug 13 Add cross-version constructor template, plus some private helper
+// functions; add constructor to exception class to take custom
+// messages (Daryle Walker)
 // 25 Aug 13 Add constexpr qualification wherever possible (Daryle Walker)
 // 05 May 12 Reduced use of implicit gcd (Mario Lang)
 // 05 Nov 06 Change rational_cast to not depend on division between different
@@ -97,6 +100,7 @@
 {
 public:
     explicit bad_rational() : std::domain_error("bad rational: zero denominator") {}
+ explicit bad_rational( char const *what ) : std::domain_error( what ) {}
 };
 
 template <typename IntType>
@@ -144,6 +148,16 @@
     rational(param_type n) : num(n), den(1) {}
     rational(param_type n, param_type d) : num(n), den(d) { normalize(); }
 
+#ifndef BOOST_NO_MEMBER_TEMPLATES
+ template < typename NewType >
+ BOOST_CONSTEXPR explicit
+ rational( rational<NewType> const &r )
+ : num( r.numerator() ), den( is_normalized(int_type( r.numerator() ),
+ int_type( r.denominator() )) ? r.denominator() :
+ throw bad_rational("bad rational: denormalized conversion") )
+ {}
+#endif
+
     // Default copy constructor and assignment are fine
 
     // Add assignment from IntType
@@ -209,12 +223,29 @@
     IntType num;
     IntType den;
 
+ // Helper functions
+ static BOOST_CONSTEXPR
+ int_type inner_gcd( param_type a, param_type b, int_type const &zero =
+ int_type(0) )
+ { return b == zero ? a : inner_gcd(b, a % b, zero); }
+
+ static BOOST_CONSTEXPR
+ int_type inner_abs( param_type x ) { return x < int_type(0) ? -x : +x; }
+
     // Representation note: Fractions are kept in normalized form at all
     // times. normalized form is defined as gcd(num,den) == 1 and den > 0.
     // In particular, note that the implementation of abs() below relies
     // on den always being positive.
     bool test_invariant() const;
     void normalize();
+
+ static BOOST_CONSTEXPR
+ bool is_normalized( param_type n, param_type d, int_type const &zero =
+ int_type(0), int_type const &one = int_type(1) )
+ {
+ return d > zero && ( n != zero || d == one ) && inner_abs( inner_gcd(n,
+ d, zero) ) == one;
+ }
 };
 
 // Assign in place

Modified: trunk/libs/rational/rational.html
==============================================================================
--- trunk/libs/rational/rational.html Tue Aug 27 03:51:27 2013 (r85483)
+++ trunk/libs/rational/rational.html 2013-08-27 06:35:26 EDT (Tue, 27 Aug 2013) (r85484)
@@ -58,6 +58,8 @@
     constexpr rational(); // Zero
     constexpr rational(I n); // Equal to n/1
               rational(I n, I d); // General case (n/d)
+ template&lt;typename J&gt;
+ constexpr explicit rational(const rational&lt;J&gt; &amp;r); // Cross-instantiation
 
     // Normal copy constructors and assignment operators
 
@@ -280,23 +282,37 @@
 two-argument constructor, any needed conversions are evaluated independently,
 of course.) The components are stored in normalized form.
 
-<p>This implies that the following statements are valid:
+<p>Rationals can also be constructed from another rational. When the source and
+destination underlying integer types match, the automatically-defined copy- or
+move-constructor is used. Otherwise, a converting constructor template is used.
+The constructor does member-wise initialization of the numerator and denominator.
+Component-level conversions that are marked <code>explicit</code> are fine. When
+the conversion ends up value-preserving, it is already normalized; but a check
+for normalization is performed in case value-preservation is violated.
+
+<p>These imply that the following statements are valid:
 
 <pre>
     I n, d;
     rational&lt;I&gt; zero;
     rational&lt;I&gt; r1(n);
     rational&lt;I&gt; r2(n, d);
+ rational&lt;J&gt; r3(r2); // assuming J(n) and J(d) are well-formed
 </pre>
 
-<p>The no- and single-argument constructors are marked as <code>constexpr</code>,
-making them viable in constant-expressions when the initializers (if any) are
-also constant expressions (and the necessary operations from the underlying
-integer type are <code>constexpr</code>-enabled).
+<p>The no-argument constructor, single-argument constructor, and cross-version
+constructor template are marked as <code>constexpr</code>, making them viable in
+constant-expressions when the initializers (if any) are also constant
+expressions (and the necessary operations from the underlying integer type(s)
+are <code>constexpr</code>-enabled).
 
 <p>The single-argument constructor is <em>not</em> declared as explicit, so
 there is an implicit conversion from the underlying integer type to the
-rational type.
+rational type. The two-argument constructor can be considered an implicit
+conversion with C++11's uniform initialization syntax, since it is also not
+declared explicit. The cross-version constructor template is declared explicit,
+so the direction of conversion between two rational instantiations must be
+specified.
 
 <h3><a name="Arithmetic operations">Arithmetic operations</a></h3>
 All of the standard numeric operators are defined for the <b>rational</b>
@@ -312,10 +328,12 @@
     == !=
     &lt; &gt;
     &lt;= &gt;=
+
+ Unary: + - !
 </pre>
 
-<p>So far, only <code>operator ==</code> and unary <code>operator +</code> are
-<code>constexpr</code>-enabled.
+<p>So far, only <code>operator ==</code>, unary <code>operator +</code>, and
+<code>operator !</code> are <code>constexpr</code>-enabled.
 
 <h3><a name="Input and Output">Input and Output</a></h3>
 Input and output operators <tt>&lt;&lt;</tt> and <tt>&gt;&gt;</tt>
@@ -347,11 +365,12 @@
 use like numeric or pointer operations or as a <code>switch</code> condition.
 
 <p>There are <em>no other</em> implicit conversions from a rational
-type. However, there is an explicit type-conversion function,
-<tt>rational_cast&lt;T&gt;(r)</tt>. This can be used as follows:
+type. Besides the explicit cross-version constructor template, there is an
+explicit type-conversion function, <tt>rational_cast&lt;T&gt;(r)</tt>. This can
+be used as follows:
 
 <pre>
- rational r(22,7);
+ rational&lt;int&gt; r(22,7);
     double nearly_pi = boost::rational_cast&lt;double&gt;(r);
 </pre>
 
@@ -359,13 +378,15 @@
 source rational's numerator or denominator cannot be safely cast to the
 appropriate floating point type, or if the division of the numerator and
 denominator (in the target floating point type) does not evaluate correctly.
+Also, since this function has a custom name, it cannot be called in generic code
+for trading between two instantiations of the same class template, unlike the
+cross-version constructor.
 
 <p>In essence, all required conversions should be value-preserving, and all
 operations should behave "sensibly". If these constraints cannot be met, a
 separate user-defined conversion will be more appropriate.
 
-<p>Boolean conversion, <tt>rational_cast</tt>, and the explicitly written
-<code>operator !</code> are <code>constexpr</code>-enabled.
+<p>Boolean conversion and <tt>rational_cast</tt> are <code>constexpr</code>-enabled.
 
 <p><em>Implementation note:</em>
 
@@ -481,7 +502,9 @@
 denominator of zero, the exception <tt>boost::bad_rational</tt> (a subclass of
 <tt>std::domain_error</tt>) is thrown. This should only occur if the user
 attempts to explicitly construct a rational with a denominator of zero, or to
-divide a rational by a zero value.
+divide a rational by a zero value. The exception can also be thrown during a
+cross-instantiation conversion, when at least one of the components ends up not
+being value-preserved and the new combination is not considered normalized.
 
 <p>In addition, if operations on the underlying integer type can generate
 exceptions, these will be propogated out of the operations on the rational
@@ -525,8 +548,8 @@
 unlimited precision integer class. With such a class, rationals are always
 exact, and no problems arise with precision loss, overflow or underflow.
 
-<p>Unfortunately, the C++ standard does not offer such a class (and neither
-does boost, at the present time). It is therefore likely that the rational
+<p>Unfortunately, the C++ standard does not offer such a class <s>(and neither
+does boost, at the present time)</s>. It is therefore likely that the rational
 number class will in many cases be used with limited-precision integer types,
 such as the built-in <tt>int</tt> type.
 
@@ -679,9 +702,9 @@
 
 <p>Daryle Walker provided a Boolean conversion operator, so that a rational can
 be used in the same Boolean contexts as the built-in numeric types, in December
-2005.
+2005. He added the cross-instantiation constructor template in August 2013.
 
-<p>Revised August 25, 2013</p>
+<p>Revised August 27, 2013</p>
 
 <p>&copy; Copyright Paul Moore 1999-2001; &copy; Daryle Walker 2005, 2013.
 Permission to copy, use, modify, sell and distribute this document is granted

Modified: trunk/libs/rational/test/rational_test.cpp
==============================================================================
--- trunk/libs/rational/test/rational_test.cpp Tue Aug 27 03:51:27 2013 (r85483)
+++ trunk/libs/rational/test/rational_test.cpp 2013-08-27 06:35:26 EDT (Tue, 27 Aug 2013) (r85484)
@@ -19,6 +19,7 @@
 // since he hasn't been in contact for years.)
 
 // Revision History
+// 27 Aug 13 Add test for cross-version constructor template (Daryle Walker)
 // 23 Aug 13 Add bug-test of narrowing conversions during order comparison;
 // spell logical-negation in it as "!" because MSVC won't accept
 // "not" (Daryle Walker)
@@ -429,6 +430,7 @@
 ::boost::rational<long> dummy3;
 ::boost::rational<MyInt> dummy4;
 ::boost::rational<MyOverflowingUnsigned> dummy5;
+::boost::rational<unsigned> dummy6;
 
 // Should there be regular tests with unsigned integer types?
 
@@ -903,6 +905,44 @@
      MyOverflowingUnsigned(1u) );
 }
 
+#ifndef BOOST_NO_MEMBER_TEMPLATES
+// Cross-conversion constructor test
+BOOST_AUTO_TEST_CASE( rational_cross_constructor_test )
+{
+ // This template will be repeated a lot.
+ using boost::rational;
+
+ // Create a bunch of explicit conversions.
+ rational<int> const half_i( 2, 4 );
+ rational<unsigned> const half_u( half_i );
+ rational<MyInt> const half_mi( half_i );
+ rational<MyOverflowingUnsigned> const half_mu1(half_u), half_mu2(half_mi);
+
+ BOOST_CHECK_EQUAL( half_u.numerator(), 1u );
+ BOOST_CHECK_EQUAL( half_u.denominator(), 2u );
+ BOOST_CHECK_EQUAL( half_mi.numerator(), MyInt(1) );
+ BOOST_CHECK_EQUAL( half_mi.denominator(), MyInt(2) );
+ BOOST_CHECK_EQUAL( half_mu1.numerator(), MyOverflowingUnsigned(1u) );
+ BOOST_CHECK_EQUAL( half_mu1.denominator(), MyOverflowingUnsigned(2u) );
+ BOOST_CHECK_EQUAL( half_mu2.numerator(), MyOverflowingUnsigned(1u) );
+ BOOST_CHECK_EQUAL( half_mu2.denominator(), MyOverflowingUnsigned(2u) );
+
+#if 0
+ // This will fail since it needs an implicit conversion.
+ // (Try it if your compiler supports C++11 lambdas.)
+ BOOST_CHECK( [](rational<unsigned> x){return !!x;}(half_i) );
+#endif
+
+ // Translation from a built-in unsigned type to a signed one is
+ // implementation-defined, so hopefully we won't get a trap value.
+ // (We're counting on static_cast<int>(UINT_MAX) being negative.)
+ rational<unsigned> const too_small( 1u, UINT_MAX );
+ rational<int> receiver;
+
+ BOOST_CHECK_THROW( receiver=rational<int>(too_small), boost::bad_rational );
+}
+#endif // BOOST_NO_MEMBER_TEMPLATES
+
 // Dice tests (a non-main test)
 BOOST_AUTO_TEST_CASE_TEMPLATE( dice_roll_test, T, all_signed_test_types )
 {


Boost-Commit list run by bdawes at acm.org, david.abrahams at rcn.com, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk