为何MSVC编译T<void(ClassName)>模板代码失败?如何修复?
问题描述
以下C++代码在GCC或Clang编译器中可正常编译,但使用MSVC编译时会触发一系列语法错误:
#include <iostream> #include <algorithm> using namespace std; namespace SMSpp_di_unipi_it { #define SMSpp_insert_in_factory_h \ static class _init { public: _init(); } _initializer; \ const std::string & private_name() const; \ static const std::string & _private_name() namespace SMSpp_type_traits { template< typename T > struct t; template< typename T , class U > struct t< T( U ) > { using type = U; }; } inline std::string && SMSpp_classname_normalise( std::string && str ) { str.erase( std::remove_if( str.begin() , str.end() , ::isspace ) , str.end() ); while( str.front() == '(' ) { str.pop_back(); str.erase( 0 , 1 ); } return( std::move( str ) ); } #define SMSpp_insert_in_factory_cpp_1_t( ClassName ) \ template<> \ const std::string & \ SMSpp_type_traits::t<void(ClassName)>::type::_private_name( void ) { \ static const std::string _name( SMSpp_classname_normalise( \ std::string( #ClassName ) ) ); \ return( _name ); \ } \ \ template<> \ const std::string & \ SMSpp_type_traits::t<void(ClassName)>::type::private_name( void ) const { \ return( SMSpp_type_traits::t<void(ClassName)>::type::_private_name() ); \ } \ } using namespace SMSpp_di_unipi_it; template< class T = void , int i = 0 > class DummyBlockT { public: DummyBlockT( ) {} private: SMSpp_insert_in_factory_h; }; SMSpp_insert_in_factory_cpp_1_t( ( ( ( ( DummyBlockT< std::pair< double , int > > ) ) ) ) ); int main() { return 0; }
MSVC编译错误信息:
C:\smspp-project\SMS++\src\Configuration.cpp(37,1): error C2059: syntax error: ')' [C:\smspp-project\cmake-build-debug\SMS++\SMS++.vcxproj]
C:\smspp-project\SMS++\src\Configuration.cpp(37,1): error C2062: type 'unknown-type' unexpected [C:\smspp-project\cmake-build-debug\SMS++\SMS++.vcxproj]
C:\smspp-project\SMS++\src\Configuration.cpp(37,1): error C2039: 'type': is not a member of 'global namespace'' [C:\smspp-project\cmake-build-debug\SMS++\SMS++.vcxproj] C:\smspp-project\SMS++\src\Configuration.cpp(37,1): error C3083: ' C:\smspp-project\SMS++\src\Configuration.cpp(37,1): error C2039: '_private_name': is not a member of 'global namespace'' [C:\smspp-project\cmake-build-debug\SMS++\SMS++.vcxproj]
C:\smspp-project\SMS++\src\Configuration.cpp(37,1): error C2143: syntax error: missing ';' before '{' [C:\smspp-project\cmake-build-debug\SMS++\SMS++.vcxproj]
C:\smspp-project\SMS++\src\Configuration.cpp(37,1): error C2447: '{': missing function header (old-style formal list?) [C:\smspp-project\cmake-build-debug\SMS++\SMS++.vcxproj]
C:\smspp-project\SMS++\src\Configuration.cpp(37,1): error C2039: 'private_name': is not a member of '`global namespace'' [C:\smspp-project\cmake-build-debug\SMS++\SMS++.vcxproj]
C:\smspp-project\SMS++\src\Configuration.cpp(37,1): error C2988: unrecognizable template declaration/definition [C:\smspp-project\cmake-build-debug\SMS++\SMS++.vcxproj]
C:\smspp-project\SMS++\src\Configuration.cpp(37,1): error C2059: syntax error: '}' [C:\smspp-project\cmake-build-debug\SMS++\SMS++.vcxproj]
不使用typedef的修复方案
问题根源是MSVC预处理器无法正确解析宏参数中的嵌套模板尖括号(如DummyBlockT<std::pair<double, int>>),外层多层括号会进一步打乱解析逻辑。
解决方法是给宏增加一层辅助展开,让MSVC先完整解析参数类型,再代入模板特化代码:
// 修改原宏定义,增加辅助宏 #define SMSpp_INSERT_IN_FACTORY_CPP_1_T_HELPER(ClassName) \ template<> \ const std::string & \ SMSpp_type_traits::t<void(ClassName)>::type::_private_name(void) { \ static const std::string _name(SMSpp_classname_normalise(std::string(#ClassName))); \ return(_name); \ } \ \ template<> \ const std::string & \ SMSpp_type_traits::t<void(ClassName)>::type::private_name(void) const { \ return(SMSpp_type_traits::t<void(ClassName)>::type::_private_name()); \ } #define SMSpp_insert_in_factory_cpp_1_t(ClassName) SMSpp_INSERT_IN_FACTORY_CPP_1_T_HELPER(ClassName)
同时,宏调用时去掉多余的外层括号,直接传递类型:
SMSpp_insert_in_factory_cpp_1_t(DummyBlockT<std::pair<double, int>>);
辅助宏会触发一次额外的预处理器展开,让MSVC正确识别嵌套模板的完整类型,避免尖括号被错误拆分。
必须使用typedef时的处理(保留原类型名)
如果业务逻辑要求必须用typedef,同时需要静态函数返回原类型名而非别名,可通过以下两种方式实现:
方法1:显式传递原类型字符串
修改宏以支持同时接收别名和原类型的字符串:
#define SMSpp_insert_in_factory_cpp_1_t_with_alias(AliasName, OriginalTypeName) \ template<> \ const std::string & \ SMSpp_type_traits::t<void(AliasName)>::type::_private_name(void) { \ static const std::string _name(SMSpp_classname_normalise(std::string(OriginalTypeName))); \ return(_name); \ } \ \ template<> \ const std::string & \ SMSpp_type_traits::t<void(AliasName)>::type::private_name(void) const { \ return(SMSpp_type_traits::t<void(AliasName)>::type::_private_name()); \ }
使用方式:
// 定义别名 using DummyAlias = DummyBlockT<std::pair<double, int>>; // 调用宏,传入别名和原类型的字符串 SMSpp_insert_in_factory_cpp_1_t_with_alias(DummyAlias, "DummyBlockT<std::pair<double, int>>");
方法2:利用预处理器字符串化
通过多层宏展开实现原类型的字符串化,避免手动编写字符串:
// 字符串化辅助宏 #define STRINGIFY(x) #x #define STRINGIFY_EXPAND(x) STRINGIFY(x) // 支持别名的宏 #define SMSpp_insert_in_factory_cpp_1_t_alias(AliasName, OriginalType) \ template<> \ const std::string & \ SMSpp_type_traits::t<void(AliasName)>::type::_private_name(void) { \ static const std::string _name(SMSpp_classname_normalise(std::string(STRINGIFY_EXPAND(OriginalType)))); \ return(_name); \ } \ \ template<> \ const std::string & \ SMSpp_type_traits::t<void(AliasName)>::type::private_name(void) const { \ return(SMSpp_type_traits::t<void(AliasName)>::type::_private_name()); \ }
使用方式:
typedef DummyBlockT<std::pair<double, int>> DummyAlias; SMSpp_insert_in_factory_cpp_1_t_alias(DummyAlias, DummyBlockT<std::pair<double, int>>);
两种方法都能让_private_name返回原类型名经过规范化后的字符串,而非别名。
内容的提问来源于stack exchange,提问作者dmeoli

