Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent converting uint64_t to uint16_t

Tags:

c++

c++11

clang

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;
}
like image 694
user855 Avatar asked Jun 01 '15 20:06

user855


People also ask

Is widening conversion always safe?

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.

What kind of type conversion is not safe?

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.


3 Answers

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;
like image 108
galop1n Avatar answered Oct 23 '22 14:10

galop1n


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.

like image 9
vsoftco Avatar answered Oct 23 '22 14:10

vsoftco


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;
}
like image 6
Alexander Revo Avatar answered Oct 23 '22 16:10

Alexander Revo