简体   繁体   中英

Prevent converting uint64_t to uint16_t

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;
}

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 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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM