17

Is there a "safe" alternative to static_cast in C++11/14 or a library which implements this functionality?

By "safe" I mean the cast should only allow casts which do not lose precision. So a cast from int64_t to int32_t would only be allowed if the number fits into a int32_t and else an error is reported.

HelpingHand
  • 1,294
  • 11
  • 27
Andreas Pasternak
  • 1,250
  • 10
  • 18
  • 7
    You want this to be a run-time error? Or to be a compile-time warning/error that there might be a loss of data/precision? – Kevin Oct 17 '18 at 14:56
  • 1
    You could provide some [mcve], in what situation you'd like to use it. – Rhathin Oct 17 '18 at 14:58
  • 8
    Yes,safe alternative is not doing cast at all – Slava Oct 17 '18 at 14:58
  • 2
    There isn't anything built in to C++. Although `numeric_limits` makes this trivial to implement as a function that would throw an exception or do a run time assert. – NathanOliver Oct 17 '18 at 14:59
  • 2
    Do you have compiler warnings turned on? There are certainly situations (not static_cast, but initialization or assignment of a bigger type into a smaller type) where the compiler will warn about loss of precision. – 0x5453 Oct 17 '18 at 14:59
  • 12
    [numeric_cast<>()](https://www.boost.org/doc/libs/1_66_0/libs/numeric/conversion/doc/html/boost_numericconversion/improved_numeric_cast__.html) – Swordfish Oct 17 '18 at 14:59
  • you can cast from int32_t to int64_t .... that's safe. But then it's also redundant too. – UKMonkey Oct 17 '18 at 15:26
  • If you use curly braces (uniform initialization) then narrowing is forbidden, so, if you do `int64_t some64value{5123456789}; int32_t some32value{some64value};`, your compiler should complain if the initialisation is narrowing. – Jesper Juhl Oct 17 '18 at 15:29
  • @Kevin it's pretty difficult to have a compile time warning that only triggers if a 64 bit number doesn't fit into 32 bits. – n. m. could be an AI Nov 02 '18 at 18:24
  • @n.m. That's why I said *might be a loss*. This is what gcc's `-Wconversion` does. – Kevin Nov 02 '18 at 20:02

4 Answers4

36

There's gsl::narrow

narrow // narrow<T>(x) is static_cast<T>(x) if static_cast<T>(x) == x or it throws narrowing_error

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • 15
    Good answer. Perhaps it might be worth extending your answer to establish the **Guideline support library's** bona fides? Some readers may not be familiar with the [Standard C++ Foundation](https://isocpp.org/about), the [C++ Core Guidelines](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c-core-guidelines) or the Guideline support library and so might dismiss the `gsl` as "just another library"? – Frank Boyne Oct 17 '18 at 17:32
  • 2
    @FrankBoyne agreed, I wondered at first, how come GNU Scientific Library has C++ utilities... – Ruslan Oct 17 '18 at 20:07
24

You've got the use-case reversed.

The intended use of static_cast (and the other c++-style casts) is to indicate programmer intentions. When you write auto value = static_cast<int32_t>(value_64);, you're saying "Yes, I very much *intend* to downcast this value, possibly truncating it, when I perform this assignment". As a result, a compiler, which might have been inclined to complain about this conversion under normal circumstances (like if you'd have written int32_t value = value_64;) instead observes "well, the programmer has told me that this is what they intended; why would they lie to me?" and will silently compile the code.

If you want your C++ code to warn or throw an error on unsafe conversions, you need to explicitly not use static_cast, const_cast, reinterpret_cast, and let the compiler do its job. Compilers have flags that change how warnings are treated (downcasting int64_t to int32_t usually only results in a Warning), so make sure you're using the correct flags to force warnings to be treated as errors.

Xirema
  • 19,889
  • 4
  • 32
  • 68
  • OP seems to be aware that truncation is in general possible, but wants an indication *when it occurs* – Caleth Oct 17 '18 at 15:05
  • 6
    @Caleth And my advice to them is to *not* use these casts if they want such an indication. Compilers will normally warn when a narrowing conversion occurs without being explicitly casted. – Xirema Oct 17 '18 at 15:07
  • 3
    @Xirema: (A) Compilers often don't warn, and (B) compilers often overwarn when casting a `long` to a `char`, but the developer knows the value is always between 0 and 100. – Mooing Duck Oct 17 '18 at 17:31
  • 7
    @MooingDuck If you know the value is always 0-100 then use static_cast, that's what it's for... – user253751 Oct 17 '18 at 22:20
  • 3
    @immibis: Let me correct myself; The developer knows the value _ought to be_ between 0 and 100, but wants an exception to be thrown if that assumption is determined to be incorrect. – Mooing Duck Oct 17 '18 at 23:40
  • 4
    @MooingDuck I'm not sure what you expect the compiler to do then? Either it can be outside the range (in which case you deserve the warning) or it can't (in which case use static_cast). – user253751 Oct 18 '18 at 02:19
  • 3
    @immibis I think the desired behavior is to generate some code along the lines of `if (i < 256) static_cast(i); else throw bad_cast_exception;` – Gavin S. Yancey Oct 18 '18 at 04:00
  • 1
    (In which case I think the questioner is better off just explicitly writing that code...) – Gavin S. Yancey Oct 18 '18 at 04:01
  • @immibis: I believe he expects it to behave like `dynamic_cast(basePtr)` does. Check for validity, and either cast or throw. – Mooing Duck Oct 18 '18 at 16:51
0

Assuming the question is about compile-time detection of potentially lossy conversions...

A simple tool not mentioned yet here is that list initialization doesn't allow narrowing, so you can write:

void g(int64_t n)
{
    int32_t x{n};   // error, narrowing

    int32_t g;
    g = {n};        // error, narrowing
}

NB. Some compilers in their default mode might show "warning" and continue compilation for this ill-formed code, usually you can configure this behaviour via compilation flags.

M.M
  • 138,810
  • 21
  • 208
  • 365
-1

You can create your own with sfinae. Here's an example:

template <typename T, typename U>
typename std::enable_if<sizeof(T) >= sizeof(U),T>::type 
safe_static_cast(U&& val)
{
    return static_cast<T>(val);
}

int main()
{
    int32_t y = 2;
    std::cout << safe_static_cast<int32_t>(y) << std::endl;
    std::cout << safe_static_cast<int16_t>(y) << std::endl; // compile error
}

This will compile only if the size you cast to is >= the source size.

Try it here

You can complicate this further using numeric_limits for other types and type_traits.

Notice that my solution is a compile-time solution, because you asked about static_cast, where static here refers to "determined at compile-time".

The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
  • 6
    This **a)** is a bad way of measuring precision, seeing as e.g. `sizeof(float) < sizeof(int64_t)`, and **b)** does not what the OP asked, namely _allow_ a cast to a smaller type but throw a runtime error when this overflows. – leftaroundabout Oct 17 '18 at 16:28
  • @leftaroundabout The OP didn't mention what kind of error. The correct interpretation I see is at compile-time. It's bad-design to do this at run-time!!!!! Besides, the definition of precision here is ambiguous, which is why I gave this as an example and mentioned numeric_limits and type_traits to modify it and make it fit his desired condition. – The Quantum Physicist Oct 17 '18 at 16:32
  • Well, I'd agree that throwing an exception is a bad way to handle it, but it's no good preventing all narrowing at compile time when for some application, you need to store something in a memory-constrained setting and `int16_t` is usually enough for all the values that need to be stored. – leftaroundabout Oct 17 '18 at 16:39
  • @leftaround It's probably an XY problem. It's difficult to provide a clean solution only with the information provided. – The Quantum Physicist Oct 17 '18 at 16:49
  • 2
    @TheQuantumPhysicist Disagree. Both Caleth and Swordfish got in. Indeed, both their solutions show that this is an understood and solved problem. – Lightness Races in Orbit Oct 17 '18 at 17:01
  • 1
    @LightnessRacesinOrbit I hope you realize how trivial it's to cast back and forth and see if the result hasn't changed. There are a million ways to ask this question the right way (or in a meaningful way, which is why this is an XY problem), where you don't involve a cast at compile-time. Static_cast is for compile-time, and hence relevant checks should be done at compile-time. Not run-time. That's how I see it. – The Quantum Physicist Oct 17 '18 at 17:11
  • 2
    Actually, casting back and forth to verify is **not enough**. [Differing signedness might slip through the cracks that way.](https://stackoverflow.com/questions/52863643/understanding-gslnarrow-implementation) – Deduplicator Oct 17 '18 at 21:54
  • @Deduplicator I don't know what's going on there in the link you posted. It's not so clear. But let me point out that, in their binary, ints have no signs (no sign bit), unlike floating-point. The process of changing the precision (bit-length) of an integer is called "sign-extension", which extends the number from the left with 0xfff... or 0x000... depending on whether the number is positive or negative (in 2's complement). So, no. There's no sign changing here if you preserve whether a number is signed or unsigned. If you switch between signed and unsigned, there are corner cases, for sure. – The Quantum Physicist Oct 17 '18 at 22:17
  • @TheQuantumPhysicist *even if* casting back and forth were sufficient to catch all the cases of casts losing information, it's *still* a good idea to give a name to "value checking cast" – Caleth Oct 18 '18 at 08:22
  • `int64_t` would never fit into an `int32_t` if interpreted the way you do, so why do you think that OP mentions the case explicitly, using it as an example? – pipe Oct 18 '18 at 08:28
  • @Caleth Again, I find this whole question ill-formed in many ways. Changing precision and using `static_cast` are two different things. The guy should be using `numeric_limits<>` to get the answer to his question. It's just bad style. Even if the guy is creating a serialization algorithm and wants to minimize the size, I find this a horrible way to do it. And again, my opinion. Who cares? Too much debate around my answer. Just let it go if you don't like it. – The Quantum Physicist Oct 18 '18 at 08:41
  • @pipe I presumed it's an XY problem. It's not uncommon for an OP to make mistakes and have an answer correct his thought process. I wanted to make it clear that `static_cast` is for compile-time decisions. – The Quantum Physicist Oct 18 '18 at 08:43
  • "Too much debate around my answer. Just let it go if you don't like it.", Personally commented because it's polite to explain downvotes. – pipe Oct 18 '18 at 08:48
  • @pipe Thank you. I appreciate that. – The Quantum Physicist Oct 18 '18 at 08:49
  • @TheQuantumPhysicist I don't, because it's not. Who cares? Well, the OP does, and so do I! If _you_ don't, you're free to just let it go if you don't like it :P – Lightness Races in Orbit Oct 18 '18 at 09:38
  • @LightnessRacesinOrbit There lies our disagreement. The reason I'm saying "who cares" is because in all my responses this is finally boiling down to coding-style issues. I find this a coding style issue and I explained why, and others may or may not agree. So, for that, who cares? :-) We all know what we all mean, so no reason to further the discussion – The Quantum Physicist Oct 18 '18 at 09:57