使用const值模板参数创建模板函数别名时遇编译错误
我需要基于模板参数的const性实现两个不同的函数实例,虽然清楚const值类型在函数参数里会被忽略,但f1的两个实例确实能正常实例化且地址不同。但创建f1的别名f2时出了问题——这种方式对const引用类型有效,但对const值类型无效,代码无法编译。
无法编译的简化代码
#include <iostream> #include <type_traits> template<class... ARGS_T> void f1(ARGS_T...) { std::cout << (std::is_const_v<ARGS_T> || ...) << std::endl; } template<class... ARGS_T> constexpr void(*f2)(ARGS_T...) = f1<ARGS_T...>; class C {}; int main() { f1<const int, const C>(1, C()); (*f2<const int, const C>)(3, C()); return 0; }
编译错误信息(Windows平台g++ 12.2.0)
C:/Alias/main.cpp: In instantiation of 'constexpr void (* const f2)(int, C)<const int, const C>': C:/Alias/main.cpp:15:8: required from here C:/Alias/main.cpp:9:17: error: no matches converting function 'f1' to type 'void (* const)(int, class C)' 9 | constexpr void(*f2)(ARGS_T...) = f1<ARGS_T...>; | ^~ C:/Alias/main.cpp:4:6: note: candidate is: 'template<class ... ARGS_T> void f1(ARGS_T ...)' 4 | void f1(ARGS_T...) { | ^~
疑问:f1的两个实例能正常实例化且地址不同,为什么没办法通过这种方式创建指向它们的两个不同指针?
核心原因
函数参数里的顶层const会被自动忽略——比如void f(int)和void f(const int)的函数签名完全一致。但模板实例化时,f1<const int>和f1<int>是两个独立的模板特化,因为模板参数的const性会被保留在实例化信息里。
但f2的声明有问题:当你实例化f2<const int, const C>时,函数指针的类型会自动丢弃参数的顶层const,变成void(*)(int, C);而你要赋值的f1<const int, const C>是基于const int和const C实例化的特化,它的类型是void(const int, const C)。虽然这两个函数签名等价,但模板特化的标识是基于原始模板参数的,导致编译器无法将f1<const int, const C>隐式转换为void(*)(int, C)类型的指针。
解决办法
要让f2的指针类型和f1的特化类型匹配,只需要在赋值时明确取f1的地址,让编译器精准匹配模板实例:
#include <iostream> #include <type_traits> template<class... ARGS_T> void f1(ARGS_T...) { std::cout << (std::is_const_v<ARGS_T> || ...) << std::endl; } // 加上&,明确取模板特化的地址,避免隐式转换导致的类型不匹配 template<class... ARGS_T> constexpr void(*f2)(ARGS_T...) = &f1<ARGS_T...>; class C {}; int main() { f1<const int, const C>(1, C()); (*f2<const int, const C>)(3, C()); // 函数指针支持隐式解引用,也可以直接调用 f2<const int, const C>(3, C()); return 0; }
补充说明
为什么const引用类型没问题?因为引用的const属于底层const,不会被函数签名忽略——void f(const int&)和void f(int&)是完全不同的签名,所以模板实例化后的函数指针类型和f2的声明类型完全匹配,不会出现类型不匹配的问题。
内容的提问来源于stack exchange,提问作者Battler

