Although the Cpp Core Guideline ES.48 remarked the fact to avoid casts in the codebase [1], in some cases casts are necessary. For example, a cast has to be applied to prevent unsafe conversion over arithmetic types. In this article, we will focus on different cast techniques over arithmetic types.

The C++ standard offers four different explicit casts [2], but for our purpose, we can apply only:

  • The static_cast

In addition to the previous casts, It will be interesting to analyze the behavior of two casts. The former is available in C++ since it is a heritage form of C, meanwhile, the latter is available in the well-known boost library:

  • The C-style cast
  • boost::numeric_cast

C-style cast

The C-style cast is available in the C++ language by default, but it is unsafe to use. One of the reasons to avoid the C-style cast is that it will convert the value from one type to another, similar to static_cast, but sometimes it just reinterprets the bits as something else without performing any actual conversion at all, similar to a reinterpret_cast. The C++-style cats make that more explicit, which avoids unpleasant surprises and makes the code more readable.

For this reason, the Cpp Core Guideline ES.49 advises us to prefer the native C++-style cast (also known as named cast)[3]. See also the Stroustrup's C++ Style and Technique FAQ [4].

static_cast

The overflow example using static_cast provide no errors but print a random number [5]:

Program returned: 0
-1

This fact is very annoying since the program can be run with random values without throwing an exception.

boost::numeric_cast

The overflow example using boost::numeric_cast provide a compilation error [6]:

Program returned: 139
terminate called after throwing an instance of 'boost::numeric::positive_overflow'
  what():  bad numeric conversion: positive overflow
Program terminated with signal: SIGSEGV

The boost::numeric_cast function prevents inconvenient errors by throwing an exception. This is particularly helpful in casting an arithmetic value to set a container size. For example:

  • If we use static_cast and it produces a random number due to an overflow issue, it can cause critical problems with memory allocation.
  • If we use boost::numeric_cast, an exception is thrown, preventing the allocation of random memory.

The numeric cast implementation

The boost::numeric_cast is implemented inside the boost numeric library, specifically in the boost/numeric/conversion/cast.hpp [7]. But it can be available using the header boost/cast.hpp.

template <typename Target, typename Source> inline
Target numeric_cast( Source arg )
{
    typedef conversion_traits<Target, Source>   conv_traits;
    typedef numeric_cast_traits<Target, Source> cast_traits;
    typedef converter
    <
    Target,
    Source,
    conv_traits,
                typename cast_traits::overflow_policy,
                typename cast_traits::rounding_policy,
    raw_converter<conv_traits>,
                typename cast_traits::range_checking_policy
    > converter;
    return converter::convert(arg);
}

“The function converts the arg value from the Source type to the Target type. If the conversion fails, an overflow policy is applied.”

Note that at the moment the boost library version is the 1.86.0.

Performance: static_cast vs boost::numeric_cast

C++ Performance analysis in Debug mode [8]:

Static cast:    8.2441 [us]
Boost numeric cast: 408.098 [us]
Boost numeric cast vs Static cast ratio: 49.5018

However, the boost library is very optimized and if we run the previous example in release mode (enabling the -O3 compiler flag) the numeric_cast is not slower than the static_cast:

C++ Performance analysis in Release mode [9]:

Static cast:    25.6696 [ns]
Boost numeric cast: 25.5617 [ns]
Boost numeric cast vs Static cast ratio: 0.995797

But, consider the following code [10]:

#include <boost/numeric/conversion/cast.hpp>

void apply_static_cast_test(int32_t i32) {
    int16_t i16 = static_cast<int16_t>(i32);
}

void apply_numeric_cast_test(int32_t i32) {
    int16_t i16 = boost::numeric_cast<int16_t>(i32);
}

int main() {
    int32_t i32 = 123;

    apply_static_cast_test(i32);
    apply_numeric_cast_test(i32);
};

Analyzing the assembly code for each major compiler (gcc, clang, and MVSC) it is clear that the generated assembly code for the boost numeric_cast has much more instructions than the static_cast. So, if we are using a cast in a performance critical code, be aware to not use numeric_cast.

Final remainder

If you need to cast an arithmetic value, consider the following practices:

  • C-style cast: Avoid using this, as recommended by the C++ Core Guidelines.
  • static_cast: The standard choice provided by the C++ Standard Library for casting arithmetic values.
  • boost::numeric_cast: Throws an exception if the cast cannot be performed. This is particularly useful for casting numbers in critical situations, such as determining the size of a container to prevent issues like incorrect memory allocations. This solution requires the availability of the boost library.

Note that there was a proposal to introduce the numeric cast in the standard [11].

References

[1]: CppCoreGuideline ES.48 Avoid casts https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-casts
[2]: C++ casts https://en.cppreference.com/w/cpp/language/explicit_cast
[3]: CppCoreGuideline ES.49 If you must use a cast, use a named cast https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es49-if-you-must-use-a-cast-use-a-named-cast
[4]: C++ Stroustrup https://www.stroustrup.com/bs_faq2.html#static-cast [5]: C++ example using static_cast https://godbolt.org/z/7WdMbbGTx
[6]: C++ example using boost numeric_cast https://godbolt.org/z/h78MfazM6
[7]: boost numeric_cast implementation in the official github repository (https://github.com/boostorg/numeric_conversion/blob/50a1eae942effb0a9b90724323ef8f2a67e7984a/include/boost/numeric/conversion/cast.hpp)
[8]: C++ performance analysis in Debug mode https://godbolt.org/z/GxzfWE46q
[9]: C++ performance analysis in Release mode https://godbolt.org/z/c99n9hPbs
[10]: C++ static_cast vs numeric_cast, the assembly code https://godbolt.org/z/99czxahTn
[11]: numeric_cast standard library proposal in https://github.com/qingfengxia/cpp_numeric_cast/blob/master/proposal_numeric_cast.md and https://lists.isocpp.org/std-proposals/2020/12/2114.php


Published

Category

Implementation details

Tags

Contact