如何编写静态断言确保typedef定义的loc_t关联的类型别名未变更?
现有代码里定义了这样的类型别名:
typedef uint_fast16_t loc_t;
当前环境的stdint.h中,uint_fast16_t的实际定义是:
typedef unsigned long int uint_fast16_t;
这类快速类型(fast types)的别名名称本身就是契约的一部分——开发者必须明确:该类型的实际宽度可能超过16位,但使用时必须严格遵循uint16_t的有效取值范围。
我需要写一个静态断言,确保loc_t始终遵守这个契约。说白了就是要检查:loc_t有没有被改成其他超出uint16_t范围的类型,或者说,loc_t的底层别名是不是从uint_fast16_t换成别的了。
现有的断言方案有问题:
// 这个实现有缺陷:如果uint_fast16_t被typedef成uint64_t, // 哪怕loc_t被改成uint_fast64_t,这个断言照样能通过 static_assert(std::is_same<loc_t, uint_fast16_t>::value, "loc_t的类型契约被破坏");
我真正想要的是类似下面这种伪代码的断言逻辑:
// 检查loc_t的类型别名是否和uint_fast16_t完全一致 static_assert(std::is_same<nameoftype(loc_t), name(uint_fast16_t)>::value, "");
C++标准里没有提供直接获取typedef别名名称的机制——因为typedef只是类型的“别名”,编译期会被完全替换成实际类型,编译器不会保留别名的字符串信息。所以没法直接实现你写的伪代码,但可以换两种思路满足契约检查的需求:
思路1:用自定义标签绑定契约
放弃检查别名名称,转而给loc_t的契约创建专属标签类型,通过标签约束类型的意图:
#include <type_traits> #include <cstdint> // 定义标签类型,标记“遵循uint16_t范围的fast类型”这个契约 struct LocContractTag {}; // 为uint_fast16_t绑定上面的标签 template<typename T> struct TypeContractTag {}; template<> struct TypeContractTag<uint_fast16_t> { using Tag = LocContractTag; }; // 定义loc_t using loc_t = uint_fast16_t; // 静态断言:loc_t必须绑定了正确的契约标签 static_assert(std::is_same_v<typename TypeContractTag<loc_t>::Tag, LocContractTag>, "loc_t必须是遵循uint16_t范围的fast类型");
如果有人把loc_t改成uint_fast32_t,TypeContractTag<loc_t>::Tag会找不到对应的特化,断言直接触发。
思路2:直接检查类型的范围契约
既然核心需求是保证loc_t的使用范围不超过uint16_t,可以直接检查类型的取值范围,同时约束它必须是uint_fast16_t的别名:
#include <cstdint> #include <type_traits> #include <limits> static_assert(std::numeric_limits<loc_t>::max() >= std::numeric_limits<uint16_t>::max(), "loc_t的范围必须至少覆盖uint16_t的全部取值"); // 约束loc_t必须是uint_fast16_t的别名 static_assert(std::is_same_v<std::remove_cv_t<loc_t>, uint_fast16_t>, "loc_t必须基于uint_fast16_t定义");
这个方案里,第二个断言虽然和你之前的“坏实现”看起来一样,但结合第一个断言后,如果编译器把uint_fast16_t的实际类型改成了uint64_t,你会收到提醒——此时uint_fast16_t的范围远超uint16_t,你需要重新评估是否还要继续用它作为loc_t的底层类型。
补充:非标准的别名检查方式
如果你的场景必须严格保证loc_t的底层别名就是uint_fast16_t(而不是和它实际类型相同的其他别名),只能借助编译器扩展,但这种方式是非标准的,可靠性有限:
// GCC/Clang专属扩展:检查两个类型是否是同一个typedef别名 static_assert(__builtin_types_compatible_p(loc_t, uint_fast16_t) && __builtin_choose_expr(__is_identifier(loc_t), true, false), "");
注意:这个方式依赖编译器实现,移植性差,不推荐在跨平台代码中使用。
内容的提问来源于stack exchange,提问作者Chuu

