Reputation: 19918
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;
}
Upvotes: 14
Views: 2563
Reputation: 13278
Although most answers here are technically correct, you will most likely not want the behaviour to apply only to this function, so a "code level" solution that you have to write for each of these conversion cases is probably not what you want.
On a "project/compilation level" you can add this flag to warn you about these conversions:
-Wconversion
or if you prefer directly treat them as errors:
-Werror=conversion
Upvotes: 4
Reputation: 757
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;
}
Upvotes: 6
Reputation: 283614
If you want to allow widening conversions, but forbid narrowing conversions, perhaps:
void foo(uint16_t x) {
}
template <class T>
void foo( const T&& t )
{
return foo(uint16_t{t});
}
This forces all types except uint16_t
itself to go through list-initialization, which forbids narrowing conversions.
It doesn't work so well if you already have a number of overloads, though.
Upvotes: 5
Reputation: 56547
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.
Upvotes: 9
Reputation: 8824
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;
Upvotes: 33