C++ 使用typename来修复模板编译错误
问题由来
在看Unreal Engine源码时,有一行代码我比较疑惑:
// 这是我要调用的代码
// Called to bind functionality to input
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// 我需要创建一个对应函数签名的函数, 叫MovingForwardFunc
PlayerInputComponent->BindAxis<AMyCharacter>(FName("MovingForward"), this, &MovingForwardFunc);
}
下面是这些函数的定义:
/**
* Binds a delegate function an Axis defined in the project settings.
* Returned reference is only guaranteed to be valid until another axis is bound.
*/
template<class UserClass>
FInputAxisBinding& BindAxis( const FName AxisName, UserClass* Object, typename FInputAxisHandlerSignature::TMethodPtr< UserClass > Func )
{
FInputAxisBinding AB( AxisName );
AB.AxisDelegate.BindDelegate(Object, Func);
AxisBindings.Emplace(MoveTemp(AB));
return AxisBindings.Last();
}
/* Helper typedefs for getting a member function pointer type for the delegate with a given payload */
template <typename UserClass, typename... VarTypes> using TMethodPtr = typename TMemFunPtrType<false, UserClass, RetValType(ParamTypes..., VarTypes...)>::Type;
疑惑的点在于,下面这一块代码是作为函数签名里的一个参数出现的:
typename FInputAxisHandlerSignature::TMethodPtr< UserClass >
因为在我之前的理解,typename是只能用于template的尖括号里的,所以写下这篇文章研究一下。
参考: Why typename?
使用typename来修复模板编译错误
先看个例子:
struct Empty {};
struct MyRandomClass
{
using E = Empty;
};
template<typename T>
struct TemplateExample
{
T m_T;
T::E m_Empty;
};
想象的用法是,当T为MyRandomClass时,模板实例化为:
// 这样看,没有问题
struct TemplateExample
{
MyRandomClass m_T;
MyRandomClass::E m_Empty;
};
然后,上面的模板直接编译会报错,报错位置为T::E m_Empty;
,报错信息为:
// 先给警告
warning C4346: 'E': dependent name is not a type
// 建议你加个typename
message : prefix with 'typename' to indicate a type
// 让你去查查TemplateExample的编译过程
message : see reference to class template instantiation 'TemplateExample<T>' being compiled
// 再给Error
error C2061: syntax error: identifier 'E'
error C2238: unexpected token(s) preceding ';'
Error信息很明确,这里的E,模板函数是认不出来的,因为它把E当成了变量,而不是Type。至于具体怎么改,警告其实已经告诉我了,改成:
template<typename T>
struct TemplateExample
{
T m_T;
typename T::E m_Empty;
};
所以核心原因是,编译器会默认认为Template的scope里面(即花括号里的内容)的所有出现的identifier是value,而不是类型(可以认为任何人为命名的东西都是identifier)。这个问题还有别的变种:
看这种情况,当编译器碰到Remains时,它默认认为这是变量,那么就会报错,应该是因为变量后面不可以接<FoodT>
,这里加个template关键字即可。
最后说一种特殊情况,下面这种写法是不需要加typename的,因为它是在类声明里出现的。编译器认为所有的类声明出现的东西,都是类型,而不是变量,所以这里不需要加typename:
顺便介绍一些相关编译报错出现的概念名词
Dependent scope type和dependent name
参考:https://mainfunda.com/what-are-dependent-scope-type-in-templates/
参考:https://www.ibm.com/docs/en/zos/2.2.0?topic=only-name-binding-dependent-names-c
C++里所有的变量、数据都有自己的type,但是C++里有一种type,由于它的类型依赖于模板参数的类型,所以叫做dependent type。比如下面这个:
template<typename T>
struct MFPointer
{
typedef T* ptype;// ptype是T*类型的别名
};
这里的ptype的类型就是dependent scope datatype,它需要通过scope resolution operator(即::)来获取,如果T为int,则ptype则是int*
,写法如下:
MFPointer<int>::ptype pi = nullptr;
MFPointer<float>::ptype pf = nullptr;
dependent name也是类似的,参考:https://en.cppreference.com/w/cpp/language/dependent_name
解析函数签名类型
最后再把开头的问题解决一下:
PlayerInputComponent->BindAxis<AMyCharacter>(FName("MovingForward"), this, &MovingForwardFunc);
/**
* Binds a delegate function an Axis defined in the project settings.
* Returned reference is only guaranteed to be valid until another axis is bound.
*/
template<class UserClass>
FInputAxisBinding& BindAxis( const FName AxisName, UserClass* Object, typename FInputAxisHandlerSignature::TMethodPtr< UserClass > Func )
{
FInputAxisBinding AB( AxisName );
AB.AxisDelegate.BindDelegate(Object, Func);
AxisBindings.Emplace(MoveTemp(AB));
return AxisBindings.Last();
}
模板实例化为:
FInputAxisBinding& BindAxis(const FName AxisName, AMyCharacter* Object, typename FInputAxisHandlerSignature::TMethodPtr<AMyCharacter> Func)
{
...
}
根据:
/* Helper typedefs for getting a member function pointer type for the delegate with a given payload */
template <typename UserClass, typename... VarTypes> using TMethodPtr = typename TMemFunPtrType<false, UserClass, RetValType(ParamTypes..., VarTypes...)>::Type;
可以解析出FInputAxisHandlerSignature::TMethodPtr<AMyCharacter> Func
,而关于FInputAxisHandlerSignature
又有一大堆东西:
FInputAxisHandlerSignature
/**
* Delegate signature for axis handlers.
* @AxisValue: "Value" to pass to the axis. This value will be the device-dependent, so a mouse will report absolute change since the last update,
* a joystick will report total displacement from the center, etc. It is up to the handler to interpret this data as it sees fit, i.e. treating
* joystick values as a rate of change would require scaling by frametime to get an absolute delta.
*/
DECLARE_DELEGATE_OneParam( FInputAxisHandlerSignature, float );
// Multiple-parameter versions of above delegate types:
#define DECLARE_DELEGATE_OneParam( DelegateName, Param1Type ) FUNC_DECLARE_DELEGATE( DelegateName, void, Param1Type )
/**
* Declares a delegate that can only bind to one native function at a time
*
* @note: The last parameter is variadic and is used as the 'template args' for this delegate's classes (__VA_ARGS__)
* @note: To avoid issues with macro expansion breaking code navigation, make sure the type/class name macro params are unique across all of these macros
*/
#define FUNC_DECLARE_DELEGATE( DelegateName, ReturnType, ... ) \
typedef TDelegate<ReturnType(__VA_ARGS__)> DelegateName;
// TDelegate函数的模板特化版本
/**
* Unicast delegate template class.
*
* Use the various DECLARE_DELEGATE macros to create the actual delegate type, templated to
* the function signature the delegate is compatible with. Then, you can create an instance
* of that class when you want to bind a function to the delegate.
*/
template <typename DelegateSignature, typename UserPolicy = FDefaultDelegateUserPolicy>
class TDelegate
{
static_assert(sizeof(DelegateSignature) == 0, "Expected a function signature for the delegate template parameter");
};
template <typename InRetValType, typename... ParamTypes, typename UserPolicy>
class TDelegate<InRetValType(ParamTypes...), UserPolicy> : public TDelegateBase<UserPolicy>
{
// 所以TMethodPtr源自TDelegate,
/* Helper typedefs for getting a member function pointer type for the delegate with a given payload */
template <typename UserClass, typename... VarTypes> using TMethodPtr = typename TMemFunPtrType<false, UserClass,
RetValType(ParamTypes..., VarTypes...)>::Type;
...
}
这里可以把DECLARE_DELEGATE_OneParam( FInputAxisHandlerSignature, float );
展开为:
typedef TDelegate<void(float)> FInputAxisHandlerSignature;
也就是说,FInputAxisHandlerSignature
是 TDelegate<void(float)>
类型的typedef,所以FInputAxisHandlerSignature::TMethodPtr<AMyCharacter>
相当于:
TDelegate<void(float)> TMethodPtr<AMyCharacter>
展开TMethodPtr
继续往上展开,难点在于这里TMethodPtr类里的Type是什么类型
TDelegate<void(float)> TMemFunPtrType<false, AMyCharacter>::Type
// 而这里的Type是在TMemFunPtrType类里定义的函数指针
template <typename Class, typename RetType, typename... ArgTypes>
struct TMemFunPtrType<false, Class, RetType(ArgTypes...)>
{
// Type是个函数指针, Class::*是意思难道必须是Class内部的成员函数的指针?
typedef RetType (Class::* Type)(ArgTypes...);
};
所以最后的函数签名应该是:
TDelegate<void(float)> AMyCharacter::Func;
这里的TDelegate就不深究了,其实应该是传入一个TDelegate对象,这个对象是AMyCharacter的成员函数的Wrapper,函数签名为(void(float))。
总之,这里输入的函数,返回值为void,参数为float,而且要是AMyChracter的成员函数,这里不能直接传入函数的指针,而是要通过TDelegate对象传入,不过我猜这里可以直接隐式转换,所以这么写了,果然是OK的:
PlayerInputComponent->BindAxis<AMyCharacter>(FName("MovingForward"), this, &AMyCharacter::MovingForwardFunc);
顺便说一句,感觉自己推断类型很麻烦,如果只是想知道函数签名,可以让他编译错误,看看提示是啥,比如我这么写,编译错误:
PlayerInputComponent->BindAxis<AMyCharacter>(FName("MovingForward"), this, &/*AMyCharacter::*/MovingForwardFunc);
错误信息告诉了我函数签名:
2>E:\UnrealProjects\MyCppDemo\Source\MyCppDemo\Private\MyCharacter.cpp(53): error C2276: '&': illegal operation on bound member function expression
2>E:\UnrealProjects\MyCppDemo\Source\MyCppDemo\Private\MyCharacter.cpp(53): error C2672: 'UInputComponent::BindAxis': no matching overloaded function found
2>E:\UnrealProjects\MyCppDemo\Source\MyCppDemo\Private\MyCharacter.cpp(53): error C2780: 'FInputAxisBinding &UInputComponent::BindAxis(const FName,UserClass *,TMemFunPtrType<false,UserClass,void(float)>::Type)': expects 3 arguments - 2 provided
2>E:\UE_5.0\UE_5.0\Engine\Source\Runtime\Engine\Classes\Components\InputComponent.h(906): note: see declaration of 'UInputComponent::BindAxis'
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本