Boost logo

Boost :

Subject: Re: [boost] [Safe Numerics] Review
From: John Maddock (jz.maddock_at_[hidden])
Date: 2017-03-12 19:11:04

On 12/03/2017 15:56, Robert Ramey via Boost wrote:
> On 3/12/17 7:54 AM, Peter Dimov via Boost wrote:
>> John Maddock wrote:
>>> 5) What is the purpose of class safe_literal? constepxr initialization
>>> seems to work just fine without it?
>> The idea, I believe, is that if you have
>> safe_signed_range<0, 100> x;
>> and then you do
>> auto y = x * safe_signed_literal<2>();
>> you get safe_signed_range<0, 200> as the type of y. This could probably
>> be made less elaborate with a user-defined literal, for example
>> auto y = x * 2_sf;
>> or something like that.

That would be my preferred solution - to define a user-defined suffix,
so that:


is a literal of type safe_signed_range<4,4>. No need for safe_literal
here IMO?

>> Since x is not constexpr, it's not possible (I think) to achieve this
>> result without using a separate literal type to hold the compile-time
>> constant 2.
> here is the problem:
> constexpr int i = 42; // i is constexpr and available at compile time
> constexpr const safe_int x(i); // x is constexpr and available at
> compile time
> constexpr const safe_int y(42); // y is NOT available at compile time!!!

Oh yes it is!

There is nothing at all non-constexpr about integer literals. Consider
the following toy class, that restricts values to the range [-10,10], it
performs runtime checking just like your safe class does, and still
works in constexpr:

class ten
     int m_value;
     template <class I>
     constexpr ten(I i) : m_value(i)
         if (i > 10 || i < -10)
             throw std::overflow_error("Expected value excedes +-10");
     constexpr ten(const ten& a) : m_value(a.m_value) {}

     constexpr ten& operator += (const ten& i)
         if ((i.m_value > 0) && (m_value > 0) && (10 - m_value < i.m_value))
             throw std::overflow_error("Addition results in value > 10");
         if ((i.m_value < 0) && (m_value < 0) && (10 + m_value > i.m_value))
             throw std::overflow_error("Addition results in a value < 10");
         return *this;
     explicit constexpr operator int()const { return m_value; }


constexpr ten operator+(const ten& i, const ten& b)
     ten result(i);
     return result += b;

Now given:

template <int value>
struct ten_holder {};

we can write:

         const constexpr ten i(5);
         const constexpr ten j(5);
         const constexpr ten k = j + i;
         ten_holder<static_cast<int>(k)> holder;

And it all just works, of course anything that results in ten holding an
out of range error triggers a compiler error because then a code path
that can't be executed at compile time (like a throw) is triggered.

In fact safe is partly working too:

         constexpr const boost::numeric::safe<int> sy(42);
         ten_holder<static_cast<int>(sy)> holder3; //OK sy is available
at compile time.

It's the arithmetic operators that are broken somewhere..... ah... FOUND IT!

2 functions called "dispatch" at checked_result.hpp:119 and
exception.hpp:33 need to be marked as constexpr in order for the binary
operators to be constexpr, with that change then I can now write:

         constexpr const boost::numeric::safe<int> sy(42);
         ten_holder<static_cast<int>(sy)> holder3; // OK sy is
available at compile time
         constexpr const boost::numeric::safe<int> syy(sy*sy);
         ten_holder<static_cast<int>(syy)> holder4; // OK syy is
available at compile time


BTW my experience with both constexpr and noexcept is that it's simply
not enough to test at runtime, you have to contrive compile time tests
as well, otherwise I can absolutely guarantee things will not work as
you expect - trust me I've been there, done that, got the t-shirt!

What that means in practice - and you're not going to like this - is
that every runtime test has to have a compile time / constexpr
counterpart, things that generate valid "safe" values should compile in
a constexpr context (and yes the value needs checking to, again at
compile time), while things that would throw at runtime, would be
compile-fails. It's a heck of a lot of tests - you might want to write
a short program to spew out the files (the expected compile things can
all go in one file, but expected failures each need their own test case
cpp file).

HTH, John.

> constexpr const safe_int z(safe_signed_literal<42>()); // z is NOW
> available at compile
> So the problem is that literals are not considered constexpr. I
> believe this is a problem is with the way constexpr is defined.
> Actually I have a whole rant on constexpr and const expressions. I
> believe that C++ standard has made this a lot more complex and less
> useful than it could be. But I can pursue only one hopeless quest at
> at time.
> Robert Ramey
> _______________________________________________
> Unsubscribe & other changes:
> .

This email has been checked for viruses by AVG.

Boost list run by bdawes at, gregod at, cpdaniel at, john at