在看Unreal Engine源码时,有一行代码我比较疑惑:
// 这是我要调用的代码
// Called to bind functionality to input
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// 我需要创建一个对应函数签名的函数, 叫MovingForwardFunc
PlayerInputComponent->BindAxis(FName("MovingForward"), this, &MovingForwardFunc);
}
下面是这些函数的定义:
templateFInputAxisBinding& 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(); } template using TMethodPtr = typename TMemFunPtrType ::Type;
疑惑的点在于,下面这一块代码是作为函数签名里的一个参数出现的:
typename FInputAxisHandlerSignature::TMethodPtr< UserClass >
因为在我之前的理解,typename是只能用于template的尖括号里的,所以写下这篇文章研究一下。
参考: Why typename?
先看个例子:
struct Empty {};
struct MyRandomClass
{
using E = Empty;
};
template
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' being compiled // 再给Error error C2061: syntax error: identifier 'E' error C2238: unexpected token(s) preceding ';'
Error信息很明确,这里的E,模板函数是认不出来的,因为它把E当成了变量,而不是Type。至于具体怎么改,警告其实已经告诉我了,改成:
templatestruct TemplateExample { T m_T; typename T::E m_Empty; };
所以核心原因是,编译器会默认认为Template的scope里面(即花括号里的内容)的所有出现的identifier是value,而不是类型(可以认为任何人为命名的东西都是identifier)。这个问题还有别的变种:
看这种情况,当编译器碰到Remains时,它默认认为这是变量,那么就会报错,应该是因为变量后面不可以接
最后说一种特殊情况,下面这种写法是不需要加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。比如下面这个:
templatestruct MFPointer { typedef T* ptype;// ptype是T*类型的别名 };
这里的ptype的类型就是dependent scope datatype,它需要通过scope resolution operator(即::)来获取,如果T为int,则ptype则是int*,写法如下:
MFPointer::ptype pi = nullptr; MFPointer ::ptype pf = nullptr;
dependent name也是类似的,参考:https://en.cppreference.com/w/cpp/language/dependent_name
解析函数签名类型
最后再把开头的问题解决一下:
PlayerInputComponent->BindAxis(FName("MovingForward"), this, &MovingForwardFunc); template 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::TMethodPtrFunc) { ... }
根据:
templateusing TMethodPtr = typename TMemFunPtrType ::Type;
可以解析出FInputAxisHandlerSignature::TMethodPtr Func,而关于FInputAxisHandlerSignature又有一大堆东西:
FInputAxisHandlerSignature
DECLARE_DELEGATE_OneParam( FInputAxisHandlerSignature, float ); // Multiple-parameter versions of above delegate types: #define DECLARE_DELEGATE_OneParam( DelegateName, Param1Type ) FUNC_DECLARE_DELEGATE( DelegateName, void, Param1Type ) #define FUNC_DECLARE_DELEGATE( DelegateName, ReturnType, ... ) typedef TDelegateDelegateName; // TDelegate函数的模板特化版本 template class TDelegate { static_assert(sizeof(DelegateSignature) == 0, "Expected a function signature for the delegate template parameter"); }; template class TDelegate : public TDelegateBase { // 所以TMethodPtr源自TDelegate, template using TMethodPtr = typename TMemFunPtrType ::Type; ... }
这里可以把DECLARE_DELEGATE_OneParam( FInputAxisHandlerSignature, float );展开为:
typedef TDelegateFInputAxisHandlerSignature;
也就是说,FInputAxisHandlerSignature是 TDelegate
TDelegateTMethodPtr
展开TMethodPtr
继续往上展开,难点在于这里TMethodPtr类里的Type是什么类型
TDelegateTMemFunPtrType ::Type // 而这里的Type是在TMemFunPtrType类里定义的函数指针 template struct TMemFunPtrType { // Type是个函数指针, Class::*是意思难道必须是Class内部的成员函数的指针? typedef RetType (Class::* Type)(ArgTypes...); };
所以最后的函数签名应该是:
TDelegateAMyCharacter::Func;
这里的TDelegate就不深究了,其实应该是传入一个TDelegate对象,这个对象是AMyCharacter的成员函数的Wrapper,函数签名为(void(float))。
总之,这里输入的函数,返回值为void,参数为float,而且要是AMyChracter的成员函数,这里不能直接传入函数的指针,而是要通过TDelegate对象传入,不过我猜这里可以直接隐式转换,所以这么写了,果然是OK的:
PlayerInputComponent->BindAxis(FName("MovingForward"), this, &AMyCharacter::MovingForwardFunc);
顺便说一句,感觉自己推断类型很麻烦,如果只是想知道函数签名,可以让他编译错误,看看提示是啥,比如我这么写,编译错误:
PlayerInputComponent->BindAxis(FName("MovingForward"), this, &MovingForwardFunc);
错误信息告诉了我函数签名:
2>E:UnrealProjectsMyCppDemoSourceMyCppDemoPrivateMyCharacter.cpp(53): error C2276: '&': illegal operation on bound member function expression 2>E:UnrealProjectsMyCppDemoSourceMyCppDemoPrivateMyCharacter.cpp(53): error C2672: 'UInputComponent::BindAxis': no matching overloaded function found 2>E:UnrealProjectsMyCppDemoSourceMyCppDemoPrivateMyCharacter.cpp(53): error C2780: 'FInputAxisBinding &UInputComponent::BindAxis(const FName,UserClass *,TMemFunPtrType暑期编程PK赛 得CSDN机械键盘等精美礼品!::Type)': expects 3 arguments - 2 provided 2>E:UE_5.0UE_5.0EngineSourceRuntimeEngineClassesComponentsInputComponent.h(906): note: see declaration of 'UInputComponent::BindAxis'



