问题由来

在看Unreal Engine源码时,有一行代码我比较疑惑:

// 这是我要调用的代码
// Called to bind functionality to input
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{Super::SetupPlayerInputComponent(PlayerInputComponent);// 我需要创建一个对应函数签名的函数, 叫MovingForwardFuncPlayerInputComponent->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;

也就是说,FInputAxisHandlerSignatureTDelegate<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'

C++ 使用typename来修复模板编译错误相关推荐

  1. C2248编译错误的原因和解决--VC6向VC7.1迁移真是累死人,N多编译错误

    把以前VC6先写好的类加入VC7中,编译时竟然错误多到编译器无法接受而停止,hooooo.真的好烦,要是有个工具修改外加的.h和cpp文件就好了... 遇到最多的是C2248编译错误,才发现VC7提升 ...

  2. invalid use of constructor as a template 编译错误

    在gcc 4.0.3上,下面这段代码会产生invalid use of constructor as a template的编译错误 template <typename T> class ...

  3. 【C++】c++编译错误-- C2678 二进制“=”: 没有找到接受“_Ty”类型的左操作数的运算符(或没有可接受的转换)

    [C++]c++编译错误-- C2678 二进制"=": 没有找到接受"_Ty"类型的左操作数的运算符(或没有可接受的转换) 代码: //by 鸟哥 rever ...

  4. c++编译错误-- C2678 二进制“=”: 没有找到接受“_Ty”类型的左操作数的运算符(或没有可接受的转换)

    代码: //by 鸟哥 reverse引起的编译错误 //有疑问请留言或加群 1032082534 #include<iostream> #include<algorithm> ...

  5. 编译linux内核的错误,linux内核编译错误

    原标题:linux内核编译错误 内核版本:2.6.14 交叉编译器: -linux-gcc 4.3.3 错误: arch/arm/mm/alignment.c: In function 'proc_a ...

  6. vc 6.0常见编译错误及改正方法

    最常见VC++6.0编译错误信息集合 1.fatal error C1010: unexpected end of file while looking for precompiled header ...

  7. oracle创建过程带有编译错误,警告:创建的函数带有编译错误

    警告:创建的函数带有编译错误 下面是一个<Oracle Database Java Developer's Guide>上的例子: ---------------------------- ...

  8. 18.16 gcc-3.4.5编译错误及解决方法集锦

    18.16 gcc-3.4.5编译错误及解决方法集锦 参考文章: (1)18.16 gcc-3.4.5编译错误及解决方法集锦 (2)https://www.cnblogs.com/baixu/p/10 ...

  9. Eclipse Maven 编译错误 Dynamic Web Module 3.0 requires Java 1.6 or newer 解决方案

    Eclipse Maven 编译错误 Dynamic Web Module 3.0 requires Java 1.6 or newer 解决方案 参考文章: (1)Eclipse Maven 编译错 ...

最新文章

  1. 你在付费听《说好不哭》,我在这里免费看直播还送书 | CSDN新书发布会
  2. 40款奇特的名片设计,吸引大家的眼球《上篇》
  3. 报名参加第103期设计论坛公益免费设计活动
  4. 开源界的 5 大开源许可协议
  5. 电视看板实现原理_电脑显示器如何改装成电视机?详细改装方法,修电脑师傅告诉你...
  6. linux登录界面主题,Ubuntu 12.10登录界面主题:Butterfly
  7. Razor 视图引擎学习
  8. Java之读写锁ReadWriteLock实现
  9. 马斯克:如果我不担任CEO 特斯拉就会完蛋
  10. C# Excel导入与导出
  11. 安卓调用系统拍照功能:1、启动拍照返回图片,2、启动拍照,图片存储在指定路径下
  12. 最近,华为应用市场上线了一个服务
  13. gmm聚类python_聚类算法GMM和KMeans?
  14. 视频编码-码率控制CQP/CRF/ABR/CBR/VBV
  15. 后端知识点:互联网中B端客户和C端客户的区别
  16. 纯前端项目文件部署到远程服务器
  17. 游戏数字资产复用——有哪些是你需要知道的?
  18. STM32CUBEIDE(15)----移植兆易创新SPI Nor Flash之GD25Q64Flash
  19. Annotation定义
  20. 日本老爷爷坚持17年用Excel作画,我可能用了假的Excel

热门文章

  1. ShareSDK常见问题汇总(iOS版)
  2. 国家版权局通报“剑网2018”成果:查处侵权案件544件
  3. 怪异报错:Wrong number of type arguments
  4. 公众号如何发送小程序卡片,教你一招
  5. 上下选项点击箭头html,点击上下箭头,页码发生改变(示例代码)
  6. 免编程App在线制作,让你快速制作心仪App
  7. java制作连连看教程,直击优秀开源框架灵魂
  8. 常规PID、模糊PID和神经网络PID
  9. 2017~2018学年《信息安全》考试试题(A1卷)
  10. spss modeler v18读书备忘小记