C++模板元编程中value_pack转type_pack的MSVC编译异常问题
问题描述
开发类型打包、值打包的模板元编程库时,遇到跨编译器兼容问题:代码在GCC 12.2.0(C++20标准)下能正常运行,但在MSVC 19.29.30151(用/std:c++20编译)时抛出错误C2993: 'auto': is not a valid type for non-type template parameter '__formal'。
这段代码的核心意图是实现别名模板transform_to_type,它接收一个带有type类型定义的元函数,把value_pack里的所有值转换为对应的类型,打包成type_pack。临时改成using transform_to_type = type_pack<decltype(typename meta_transformer<values>::type())...>;能让MSVC编译通过,但这种写法并不理想。
需要解决三个问题:
- 这是不是MSVC的编译器bug?
- 怎么让原代码在MSVC上正常工作?
- 有没有其他兼容MSVC的实现方式?
原代码
#include <type_traits> template <typename T> struct type_identity { using type = T; }; template <typename ...types> struct type_pack {}; template <auto ...values> struct value_pack { template <template <auto> typename meta_transformer> using transform_to_type = type_pack<typename meta_transformer<values>::type...>; //C2993 on MSVC }; template <auto i> struct type_map; template <> struct type_map<1> : type_identity<int> {}; template <> struct type_map<2.0> : type_identity<double> {}; template <> struct type_map<'c'> : type_identity<char> {}; int main() { using mapped_types = value_pack<1, 2.0, 'c'>::transform_to_type<type_map>; static_assert(std::is_same_v<mapped_types, type_pack<int, double, char>>, "Error!"); return 0; }
问题解答
1. 是否为MSVC编译器bug?
是,这属于MSVC对C20标准支持不到位的兼容性bug。根据C20标准,模板模板参数允许用auto作为非类型模板参数的占位符,GCC、Clang都已经正确实现了这个特性,但MSVC 19.29这类旧版本还没完全支持。升级到Visual Studio 2022 17.0及以上版本(对应MSVC 19.30+)就能解决这个问题。
2. 让原代码在MSVC 19.29正常工作的方法
可以通过显式指定模板模板参数的非类型参数类型占位符,适配MSVC的兼容写法。修改value_pack里的模板模板参数声明,同时同步调整type_map的模板参数结构:
#include <type_traits> template <typename T> struct type_identity { using type = T; }; template <typename ...types> struct type_pack {}; template <auto ...values> struct value_pack { // 修改模板模板参数为显式类型+值的形式 template <template <typename T, T> typename meta_transformer> using transform_to_type = type_pack<typename meta_transformer<decltype(values), values>::type...>; }; // 同步修改type_map的模板参数声明 template <typename T, T i> struct type_map; template <> struct type_map<int, 1> : type_identity<int> {}; template <> struct type_map<double, 2.0> : type_identity<double> {}; template <> struct type_map<char, 'c'> : type_identity<char> {}; int main() { using mapped_types = value_pack<1, 2.0, 'c'>::transform_to_type<type_map>; static_assert(std::is_same_v<mapped_types, type_pack<int, double, char>>, "Error!"); return 0; }
修改后,main函数里的代码不需要改动,就能在MSVC 19.29和GCC下同时编译通过。
3. 其他兼容MSVC的实现方式
如果不想修改原type_map的结构,可以新增一个中间辅助模板做适配,保持原type_map的接口不变:
#include <type_traits> template <typename T> struct type_identity { using type = T; }; template <typename ...types> struct type_pack {}; // 保留原type_map的声明和特化 template <auto i> struct type_map; template <> struct type_map<1> : type_identity<int> {}; template <> struct type_map<2.0> : type_identity<double> {}; template <> struct type_map<'c'> : type_identity<char> {}; // 新增辅助模板,适配MSVC的模板模板参数要求 template <typename T, T val> struct adapt_type_map : type_map<val> {}; template <auto ...values> struct value_pack { template <template <auto> typename meta_transformer> // 通过辅助模板间接调用原meta_transformer using transform_to_type = type_pack<typename adapt_type_map<decltype(values), values>::type...>; }; int main() { using mapped_types = value_pack<1, 2.0, 'c'>::transform_to_type<type_map>; static_assert(std::is_same_v<mapped_types, type_pack<int, double, char>>, "Error!"); return 0; }
这种方式不需要改动原type_map的特化代码,仅通过中间层完成适配,同样能在两个编译器下兼容。
内容的提问来源于stack exchange,提问作者uxsu

