Warning C26472: Don't use a static_cast for arithmetic conversions. Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1).
The advice "Don't use static_cast for arithmetic types" is a strict version of Avoid casts and prefer named casts (said in type.1).
My advice is that, in order to keep type safety strictly, static_cast should be not used for arithmetic conversions. However, in many occasions, the chance of unsafe type cast is very low or type safety can be well ensured by algorithms and programs, static_cast could be used for arithmetic conversions.
More thinking about type safety of arithmetic types is giving as following:
Brace initialization is an ideal type-safe strategy as it prohibited narrowing conversions. For gsl::narrow and gsl::narrow_cast, there are no similar narrow conversion functions in C++ Standard Library at present.
gsl::narrow ensures lossless conversion and throws gsl::narrowing_error if it's not possible.
gsl::narrow_cast clearly states that conversion can lose data and it's acceptable.
According the document of GSL, gsl::narrow_cast is a named cast that is identical to a static_cast.
Thus, it would still cause unsafe narrow conversion as it does not check the value it is converting (same as static_cast). For example,
std::uint8_t c1 = gsl::narrow_cast<std::uint8_t>(1000.5);// same as using static_cast
std::uint8_t c2 = gsl::narrow_cast<std::uint8_t>(-1.5); // same as using static_cast
The above code can be run successfully, while the narrow conversion is still unsafe.
If you do not want to use GSL and do not care too much about performance, arithmetic conversions could also be implemented by writing a simpler type-safe numeric_cast function as giving in the following code.
#include <iostream>
#include <limits>
void error(const std::string& errormessage){
std::cerr << errormessage;
throw std::runtime_error(errormessage);
}
template <typename Target, typename Source>
constexpr Target numeric_cast(Source&& src){
if (typeid(Target) != typeid(bool) &&
(src < std::numeric_limits<Target>::lowest() ||
src > std::numeric_limits<Target>::max()) )
error("Unsafe numerical cast.\n");
return static_cast<Target>(std::forward<Source>(src));
}
int main() {
//1. Numerical promotion
float f1{ 1.1f }; double dd1{ f1 }; //Suggested!
double d1{ numeric_cast<double>(f1) }; //Ok
//2. Numerical narrowing
double dd2{ 1.1 }; float f2 = numeric_cast<float>(dd2); //OK
std::size_t uu2{ 10 }; std::uint8_t u2 = numeric_cast<std::uint8_t>(uu2); //OK
//3. Numerical truncating
double d3{ 1.5 }; std::uint8_t u3 = numeric_cast<std::uint8_t>(d3); //OK
// Errors detecked
//double d4{ -1.5 }; std::uint8_t u4 = numeric_cast<std::uint8_t>(d4); //error detecked
//double dd4{ 1000 }; std::uint8_t uu4 = numeric_cast<std::uint8_t>(dd4); //error detecked
}
The numeric_cast function could be used for numerical promotion, numerical narrowing and numerical truncating simultaneously. It can also find the errors of unreasonable arithmetic conversions by check the value it is converting and the limits of converting target.
Of course, to keep typy-safety, the performance of numeric_cast is relatively lower when compared with other arithmetic conversions without value check.