Why does the following code compile in clang++?
Are there any c++ flags to prevent this from happening - I would like the compiler to throw an error because I am passing a std::uint64_t as an argument to a function that accepts std::uint16_t.
#include <cstdint>
using namespace std;
void foo(uint16_t x) {
}
int main() {
uint64_t x = 10000;
foo(x);
return 0;
}
Widening conversions (promotion) In a widening conversion, a value in a smaller variable is assigned to a larger variable with no loss of data. Because widening conversions are always safe, the compiler performs them silently and doesn't issue warnings.
On the other hand, the conversion in the opposite direction is known as explicit conversion. It needs a cast operator to convert higher data type into a smaller data type. This type of conversion is not type-safe and may result in loss of data.
you can delete a function in c++11
void foo(uint64_t) = delete;
it works by adding the signature at function overload resolution, and if it was a better match, an error occurs. You can also make it generic to allow only you original signature
template <class T> void foo( T&& ) = delete;
You can also use enable_if
as a SFINAE return parameter
#include <iostream>
#include <cstdint>
#include <type_traits>
template<typename T>
typename std::enable_if<std::is_same<T, uint16_t>::value>::type
foo(T x)
{
std::cout << "uint16_t" << std::endl;
}
template<typename T>
typename std::enable_if<!std::is_same<T, uint16_t>::value>::type
foo(T x)
{
std::cout << "rest" << std::endl;
}
int main() {
uint16_t x = 10000;
uint64_t y = 100000;
foo(x); // picks up uint16_t version
foo(y); // picks up anything else, but NOT uint16_t
return 0;
}
In this way you can have one overload that deals specifically with uint16_t
, and another overload that deals with anything else.
Here's a solution that would allow widening conversions and prevent the narrowing ones:
#include <cstdint>
#include <type_traits>
void foo(uint16_t x) {
}
template <class T>
typename std::enable_if<sizeof(uint16_t) < sizeof(T)>::type foo(const T& t) = delete;
int main() {
uint64_t x = 10000;
uint16_t y = 10000;
uint8_t z = 100;
// foo(x); // ERROR: narrowing conversion
foo(y); // OK: no conversion
foo(z); // OK: widening conversion
return 0;
}
In case you'd also like to disallow calls with arguments of signed types (conversions between signed and unsigned types are not "lossless"), you could use the following declaration instead:
#include <cstdint>
#include <type_traits>
void foo(uint16_t x) {
}
template <class T>
typename std::enable_if<(sizeof(uint16_t) < sizeof(T)) ||
(std::is_signed<T>::value != std::is_signed<uint16_t>::value)
>::type
foo(const T& t) = delete;
int main() {
uint64_t u64 = 10000;
uint16_t u16 = 10000;
uint8_t u8 = 100;
int64_t s64 = 10000;
int16_t s16 = 10000;
int8_t s8 = 100;
//foo(u64); // ERROR: narrowing conversion
foo(u16); // OK: no conversion
foo(u8); // OK: widening conversion
//foo(s64); // ERROR: narrowing conversion AND signed/unsigned mismatch
//foo(s16); // ERROR: signed/unsigned mismatch
//foo(s8); // ERROR: signed/unsigned mismatch
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With