6

I am looking for a Standard Library or Boost function that can losslessly cast a number to another primitive type and somehow inform me whether the cast was lossless (or throw an exception if it is not). Here are some examples:

auto x = lossless_cast<double>(1u); // ok, double can represent 1
auto x = lossless_cast<int>(1.2); // fail, int can't represent 1.2
auto x = lossless_cast<int>(1E200); // fail, int can't represent 1E200

boost::numeric_cast comes close in that it will pick up casts that fall out of the numeric range of the target type, but not if they are lossless but within the target type (see my 2nd example).

There is a SO question for the C language which provides some hand-rolled solutions to this problem, but I am after a boost or Standard Library solution, basically with the following functionality:

template <typename out, typename in>
out lossless_cast(in in_value)
{
  out out_value = static_cast<out>(in_value);

  if (static_cast<in>(out_value) != in_value)
    throw; // some exception

  return out_value;
}

Does this functionality exist?

Community
  • 1
  • 1
quant
  • 21,507
  • 32
  • 115
  • 211
  • 4
    Write a function that performs the cast then compares the before and after for equality. – Igor Tandetnik May 11 '15 at 01:46
  • 3
    If you are expecting a solution without cast roundtrips, this is could be a harder problem than you realize. A `float` can't represent 16,777,217, for instance. – zneak May 11 '15 at 01:49
  • 2
    @IgorTandetnik: not a good idea - in some cases that gives *undefined behaviour* (e.g. for the `lossless_cast(1E200);` case in the question). – Tony Delroy May 11 '15 at 02:19
  • Interestingly double can't represent `1.2` losslessly either.... ;) – Michael Anderson May 11 '15 at 03:33
  • @MichaelAnderson: true for mathematical 1.2, but in `lossless_cast(1.2);` the `1.2` is already the `double` approximation. You could write a `lossless_cast("1.2")` to check for the issue you mention. – Tony Delroy May 11 '15 at 03:47
  • 1
    Could whoever closed this question please give my question another read. This is not a duplicate of that question. – quant May 11 '15 at 04:07
  • @TonyD: As the questioner says, `boost::numeric_cast` will detect those undefined behavior cases and throw an exception. So just combine `boost::numeric_cast` with Igor's suggestion and you have a working solution. (And I still think this is essentially a duplicate of http://stackoverflow.com/q/3239968/) – Nemo May 11 '15 at 04:14
  • @Nemo: coincidentally, I was just writing up the same solution, though the question kind of shot itself in the foot by implying a single library-provided function was desired. Anyway, I don't believe it's a valid duplicate - from `double` to `float` is a very specific case, and avoids the undefined behaviour scenarios for `int`->`float` and `float`->`int` as well as out-of-range cases: that's most of the complexity in this question. – Tony Delroy May 11 '15 at 04:26
  • @TonyD: Fair enough. Apparently this is somewhat more subtle than I thought. Now I wonder if the current `boost::numeric_cast` implementation actually avoids the UB cases, and if not, whether the maintainers would agree that is a bug... – Nemo May 11 '15 at 04:28

1 Answers1

3

I'm pretty sure there's nothing pre-rolled in the Standard, and not aware of anything in boost but it's a big library. Any implementation using casting has to be careful of the undefined behaviour for out-of-range values per 4.9 [conv.fpint], but as boost::numeric_cast<> ostensibly handles that, you could use:

template <typename U, typename T>
inline U is_lossless(T t)
{
    U u = boost::numeric_cast<U>(t);
    T t2 = boost::numeric_cast<T>(u); // throw if now out-of-range
    if (t != t2)
        throw whatever-you-like;
    return u;
}

The need for numeric_cast<> when recovering t2 is least obvious: it ensures that u is still in range, as it's possible for a value cast from say int64_t x to double y to succeed but be approximated with an integral value in y than's out of range for an int64_t, such that casting back from double has undefined behaviour.

The legality/robustness of the above requires that boost::numeric_cast<> correctly avoids undefined behaviour, which I haven't verified.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • I am pretty sure the intent of `boost::numeric_cast` is to avoid the UB cases. Whether it actually succeeds in the current implementation is harder to say; see http://stackoverflow.com/q/25857843/ – Nemo May 11 '15 at 04:26
  • @Nemo: you'd hope that was their intent, though it may be hard to do quickly (in the sense of CPU cycles) and they might have found the undefined behaviours on the specific CPUs that boost targetted were acceptable for their portability requirements, without being Standard-mandated portable across all possible targets. I've just pulled up the code on my PC and will check out your link - cheers. – Tony Delroy May 11 '15 at 04:28
  • 1
    Since the type of `T` can be inferred from the parameter, would there be value in making `typename U` the *first* template parameter? That would allow a "casting" syntax that resembles what boost and the core language provide, I think. – Drew Dormann May 11 '15 at 14:18
  • @DrewDormann: certainly - got myself twisted up there. Cheers. – Tony Delroy May 12 '15 at 03:35