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 cast
s, 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