基于模板模板参数生成唯一ID是否属于C++未定义行为(UB)?
问题分析
你遇到的跨编译器差异不属于未定义行为,属于C++标准未明确规定的未决议题范畴。
现有标准没有对别名模板和其指代的底层模板是否属于等价的模板模板参数作出强制规定,两种实现均符合当前标准要求:
- GCC的判定逻辑是完全等价的别名模板和其底层模板视为同一模板实体,因此
S、W1::type、W2::type会匹配到同一个GetID模板实例,输出三个1。 - MSVC和Clang的判定逻辑是每个别名模板本身为独立实体,哪怕指向相同底层模板,也会生成不同的
GetID模板实例,因此输出三个不同ID。
规避方案说明
你设计的新方案是可跨编译器稳定运行的解决方案,核心是将模板ID的生成绑定到模板的Tag特化类型上,而类型的等价判定是标准明确规定的,不受别名模板的歧义影响。该方案的取舍如下:
- 优势:跨编译器行为完全一致,可自由控制别名模板与原模板是否共用同一ID,仅需调整Tag特化的实现即可。
- 劣势:每个需要生成ID的模板都需要手动编写Tag特化的样板代码。
原实现代码
inline size_t g_id = 1; template<template<typename> typename T> inline size_t GetID() { static size_t id = g_id++; return id; }
测试代码
template<template<typename> typename T> inline void print() { std::cout << GetID<T>() << "\n"; } template<typename T> struct S {}; struct W1 { template<typename A> using type = S<A>; }; struct W2 { template<typename A> using type = S<A>; }; int main() { print<S>(); print<W1::type>(); print<W2::type>(); std::cin.get(); }
规避方案实现代码
inline size_t g_id = 1; template<typename T> inline size_t GenerateID() { static size_t id = g_id++; return id; } struct Tag {}; template<template<typename...> typename T, typename... Args, typename = decltype(sizeof(T<Args...>))> inline size_t get_id_imp(int) { return T<Args...>::GetID(); } template<template<typename...> typename T, typename... Args, std::enable_if_t<sizeof...(Args) < 16, bool> = true>//16 = max template args inline size_t get_id_imp(...) { return get_id_imp<T, Args..., Tag>(0); } template<template<typename...> typename T, typename... Args, std::enable_if_t<sizeof...(Args) >= 16, bool > = true> inline size_t get_id_imp(...) { return 0; } template<template<typename...> typename T> inline size_t GetID() { return get_id_imp<T, Tag>(0); }
规避方案使用示例
template<typename T> struct X {}; template<> struct X<Tag> { static size_t GetID() { return GenerateID<X>(); } }; template<template<typename...> typename T> inline void print() { std::cout << GetID<T>() << "\n"; }
内容的提问来源于stack exchange,提问作者B.D
相关产品推荐
相关产品推荐

