为何以下C++代码可在MSVC2022编译却无法在GCC 12编译?
MSVC2022可编译但GCC12编译失败的C++模板代码原因分析
问题代码
#include <iostream> template <auto value> class getter; template <class T, class ReturnType, ReturnType(T::* func_ptr)() const> class getter<func_ptr> { public: using object_type = T; using value_type = ReturnType; constexpr ReturnType operator() (const T& val) const { return (val.*func_ptr)(); } }; template <class T, class Field, Field T::* field_ptr> class getter<field_ptr> { public: using object_type = T; using value_type = Field; constexpr const Field& operator() (const T& val) const { return val.*field_ptr; } }; struct A { A() = default; explicit A(size_t k) : key(k), attribute(k + 1) { } size_t key; size_t attribute; size_t GetKey() const { return key; } const size_t & GetKeyRef() const { return key; } }; int main() { auto g1 = getter<&A::key>(); auto g2 = getter<&A::GetKey>(); A a(5); std::cout << g1(a) << " " << g2(a) << std::endl; return 0; }
GCC12编译错误信息
MyTest.cpp: In function ‘int {anonymous}::main()’: MyTest.cpp:66:38: error: ambiguous template instantiation for ‘class {anonymous}::getter<&{anonymous}::A::GetKey>’ 66 | auto g2 = getter<&A::GetKey>(); | ^ MyTest.cpp:14:11: note: candidates are: ‘template<class T, class ReturnType, ReturnType (T::* func_ptr)() const> class {anonymous}::getter<func_ptr> [with T = {anonymous}::A; ReturnType = long unsigned int; ReturnType (T::* func_ptr)() const = &{anonymous}::A::GetKey]’ 14 | class getter<func_ptr> | ^~~~~~~~~~~~~~~~ MyTest.cpp:28:11: note: ‘template<class T, class Field, Field T::* field_ptr> class {anonymous}::getter<field_ptr> [with T = {anonymous}::A; Field = long unsigned int() const; Field T::* field_ptr = &{anonymous}::A::GetKey]’ 28 | class getter<field_ptr> | ^~~~~~~~~~~~~~~~~ MyTest.cpp:66:38: error: invalid use of incomplete type ‘class {anonymous}::getter<&{anonymous}::A::GetKey>’ 66 | auto g2 = getter<&A::GetKey>(); | ^ MyTest.cpp:11:11: note: declaration of ‘class {anonymous}::getter<&{anonymous}::A::GetKey>’ 11 | class getter; | ^~~~~~ MyTest.cpp: At global scope: MyTest.cpp:63:9: warning: ‘int {anonymous}::main()’ defined but not used [-Wunused-function] 63 | int main() | ^~~~ ninja: build stopped: subcommand failed.
原因分析
GCC在模板参数推导时,会将成员函数指针&A::GetKey同时匹配到两个特化模板:
- 第一个特化是专门针对成员函数指针
ReturnType(T::*)() const的版本,完全匹配。 - 第二个特化针对
Field T::*(通用成员指针),GCC会将Field推导为成员函数类型size_t() const,此时Field T::*等价于成员函数指针类型size_t (A::*)() const,和&A::GetKey的类型完全一致,因此GCC认为这个特化也符合匹配条件,最终导致模板实例化出现歧义。
而MSVC的模板匹配逻辑更严格,它会区分成员对象指针和成员函数指针的特化,不会将成员函数指针匹配到针对成员对象指针的特化模板上,因此不会出现歧义。
解决方案
通过SFINAE机制约束第二个特化模板,仅当模板参数是成员对象指针时才生效,排除成员函数指针的情况。以下是两种实现方式:
C++20版本(推荐)
#include <iostream> #include <type_traits> template <auto value> class getter; template <class T, class ReturnType, ReturnType(T::* func_ptr)() const> class getter<func_ptr> { public: using object_type = T; using value_type = ReturnType; constexpr ReturnType operator() (const T& val) const { return (val.*func_ptr)(); } }; template <class T, class Field, Field T::* field_ptr> requires std::is_member_object_pointer_v<Field T::*> class getter<field_ptr> { public: using object_type = T; using value_type = Field; constexpr const Field& operator() (const T& val) const { return val.*field_ptr; } }; // 后续A结构体和main函数不变
C++17及之前版本
#include <iostream> #include <type_traits> template <auto value> class getter; template <class T, class ReturnType, ReturnType(T::* func_ptr)() const> class getter<func_ptr> { public: using object_type = T; using value_type = ReturnType; constexpr ReturnType operator() (const T& val) const { return (val.*func_ptr)(); } }; template <class T, class Field, Field T::* field_ptr, typename = std::enable_if_t<std::is_member_object_pointer_v<Field T::*>>> class getter<field_ptr> { public: using object_type = T; using value_type = Field; constexpr const Field& operator() (const T& val) const { return val.*field_ptr; } }; // 后续A结构体和main函数不变
添加约束后,GCC会排除成员函数指针对第二个特化的匹配,仅匹配第一个针对成员函数指针的特化,消除歧义,代码即可正常编译。
内容的提问来源于stack exchange,提问作者Alexey Starinsky
相关产品推荐
相关产品推荐

