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:

4_safe

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
{
private:
     int m_value;
public:
     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:
> http://lists.boost.org/mailman/listinfo.cgi/boost
> .
>

---
This email has been checked for viruses by AVG.
http://www.avg.com

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk