防止将 uint64_t 转换为 uint16_t

Posted

技术标签:

【中文标题】防止将 uint64_t 转换为 uint16_t【英文标题】:Prevent converting uint64_t to uint16_t 【发布时间】:2015-06-01 20:53:56 【问题描述】:

为什么下面的代码在clang++中编译?

是否有任何 c++ 标志来防止这种情况发生 - 我希望编译器抛出错误,因为我将 std::uint64_t 作为参数传递给接受 std::uint16_t 的函数。

#include <cstdint>
using namespace std;

void foo(uint16_t x) 


int main() 
    uint64_t x = 10000;
    foo(x);
    return 0;

【问题讨论】:

C++ 中允许缩窄转换,无符号缩窄转换是明确定义的 添加 -Werror=conversion 将导致您的示例无法编译。 ..或者只是-Wall -Wextra -Werror -pedantic @JohnBollinger 简单:容易出错。 uint16_tuint64_t不同的类型。这些隐式转换引起了足够的头痛。我建议始终将警告级别设置得足够高以涵盖此类情况。 【参考方案1】:

你可以在 c++11 中删除一个函数

void foo(uint64_t) = delete;

它通过在函数重载解析时添加签名来工作,如果匹配得更好,则会发生错误。您也可以将其设为通用,只允许您的原始签名

template <class T> void foo( T&& ) = delete;

【讨论】:

这对提出的问题有何反应? 他问如何防止它发生,这将有效地防止它。 @JohnBollinger:如果添加了这个,给定的程序将无法编译,不是吗? 因为这是他的要求,如果有人向接收 uint16 的函数提供 uint64,编译器会抛出错误 首先问为什么他的代码会编译,这个答案没有解决。不过,这很公平,这确实回答了第二部分。【参考方案2】:

你也可以使用enable_if作为SFINAE返回参数

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

通过这种方式,您可以拥有一个专门处理uint16_t 的重载,以及另一个处理其他任何事情的重载。

【讨论】:

这很好,但它也阻止了扩大转换(与其他答案一样)。如果该函数可以接受例如uint8_t 值,甚至可以接受其值适合uint16_t 的文字,那就太好了。有人建议使用std::common_typehere,但我认为这不适用于uint16_t,因为整数提升到int @MattMcNabb 嗯,这似乎有点棘手,实际上很有趣:如何接受任何“适合”参数的东西。好吧,有一个“方法”:蛮力,明确指定模板,如foo&lt;int16_t&gt;(x);。不过不是很漂亮。【参考方案3】:

这是一个允许扩大转化率并防止缩小转化率的解决方案:

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

如果您还想禁止使用有符号类型的参数进行调用(有符号和无符号类型之间的转换不是“无损的”),您可以改用以下声明:

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

【讨论】:

【参考方案4】:

如果您想允许扩大转化,但禁止缩小转化,也许:

void foo(uint16_t x) 


template <class T>
void foo( const T&& t )

    return foo(uint16_tt);

这会强制除uint16_t 本身之外的所有类型都进行列表初始化,从而禁止缩小转换。

但是,如果您已经有许多重载,它就不会那么好用了。

【讨论】:

不幸的是,缩小不需要导致错误,参见例如***.com/q/28466069/3093378。例如,这有效:ideone.com/DHqBYL。也许我错过了什么。 @vsoftco “如果需要缩小转换(见下文)将元素转换为T,则程序格式错误”,来自 8.5.4 @Barry 是的,它的格式不正确,但是编译器不需要发出错误。并且实际上编译了程序。 @vsoftco:如果您将构建设置配置为在遇到格式不正确的程序时继续,您将得到您想要的。符合标准的编译器永远不会对此保持沉默,除非特别配置为不这样做,否则大多数都会出错。 是的,确实,您会在 g++ 上收到带有 -Wall -Wextra 的警告。然而,我发现非常奇怪的是,如果不启用警告,您将得不到任何诊断,因为我确信该程序甚至不会编译。【参考方案5】:

虽然这里的大多数答案在技术上都是正确的,但您很可能不希望该行为仅适用于此函数,因此您必须为每种转换情况编写的“代码级”解决方案可能不是您想要的.

在“项目/编译级别”上,您可以添加此标志来警告您这些转换:

-Wconversion

或者如果您更愿意直接将它们视为错误:

-Werror=conversion

【讨论】:

请注意,这不会警告(在 GCC 4.9.2 中测试)关于从有符号 int16_t 到无符号 uint16_t 的转换,这也可能导致将无效值传递给函数。您可以添加-Wsign-conversion(或-Werror=sign-conversion)来检测签名的变化。

以上是关于防止将 uint64_t 转换为 uint16_t的主要内容,如果未能解决你的问题,请参考以下文章

如何将字节从 uint64_t 转换为 double?

将 uint8_t 数组转换为 C 中的 uint16_t 值

无法将 eosio::name 转换为 uint64_t

将 uint16_t 转换为 char[2] 以通过套接字(unix)发送

将 int16_t 变量转换为 uint8_t 以传递给函数

为啥在 gcc 和 clang 上通过优化将大 double 转换为 uint16_t 会给出不同的答案