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'
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本文件中包含了DirectX修复工具中一些常见问题及其解答,如您存在问题,请首先查看以下解答是否能解决您的问题。 This file includes some frequently asked questions and answers about DirectX Repair. If you have any problems with the programme, please check first if there are answers below (English translation is at bottom). Thank you. 问题1:XP系统上运行软件时出现0xc0000135的错误,怎么回事? 答:Windows XP SP3系统用户需先安装Microsoft .NET Framework 2.0或更高版本才可运行本程序,详情请见“致Windows XP用户.txt”文件。 问题2:我下载的是标准版或是在线修复版,怎么将程序升级成增强版? 答:首先来说,各个版本之间,主程序(即exe文件)完全相同,标准版与增强版相比,只是缺少相应的扩展数据包,因此无法进行增强式修复(即修复c++)。因此,可以通过补全扩展包的形式使标准版直接成为增强版。本程序自V3.5版起,自带扩展功能。只要在主界面的“工具”菜单下打开“选项”对话框,找到“扩展”标签,点击其中的“开始扩展”按钮即可。扩展过程需要Internet连接,扩展成功后新的数据包可立即生效。扩展用时根据网络速度不同而不同,最快仅需数秒,最慢需要数分钟,烦请耐心等待。 问题3:我的网络连接正常,但为什么扩展总是失败并提示请连接到Internet? 答:这可能是由于扩展过程被电脑上的杀毒软件或防火墙拦截导致的。从V4.0版起针对此问题进行了优化,只需点击“扩展”界面左上角的小锁图标切换为加密链接,即可避免大部分错误。 问题4:我在有的电脑上使用标准版或在线修复修复DirectX后,程序弹出c++组件仍异常的提示,让我使用增强版再修复;而在有些其他电脑上使用标准版修复完成后,却没有这个提示(此时我感觉c++仍有问题)。这是什么原因? 答:本程序致力于解决0xc000007b错误,因此只有在程序检测到系统中c++存在异常,可能导致0xc000007b问题,而修复时又没有使用增强版修复相应c++时,才会弹出此提示。而对于那些根本没有安装c++的系统,程序则不会提示。理论上讲,本程序完全可以解决c++未安装所带来的任何错误(如提示缺少msvcr140.dll文件等),但之所以程序在这些系统上不做任何提示,是考虑到绝大部分电脑都会缺失c++组件,如果均进行提示,则此提示将变成必出现的提示,所有用户都需要使用增强版进行再次修复,失去了标准版存在的意义。 问题5:部分文件修复失败怎么办? 答:可以以安全模式引导系统(具体方法百度可查),然后再用本程序进行修复即可成功。 问题6:全部文件的状态都是下载失败或失败,这是怎么回事? 答:在极个别的电脑上,由于系统核心组件异常,导致程序在检测时无法调用系统组件而产生此问题。此时请在程序的“工具”菜单下“选项”对话框中,将“安全级别”改为“低”即可。更改后再进行修复即可正确完成相关操作。 问题7:该软件能支持64位操作系统吗? 答:能。程序在编程时已经充分考虑了不同系统的特性,可以完美支持64位操作系统。并且,程序有自适应功能,可以自动检测操作系统版本、位数,无需用户进行设置。 问题8:玩游戏出现闪退、黑屏、卡屏、卡死、帧数低、打太极等问题,修复后仍不能解决? 答:该问题的可能原因较多,比如DirectX有问题,c++有问题。使用DirectX修复工具增强版即可解决由这两种情况所导致的问题。如果修复后仍不能解决,则可能有三种原因:第一,游戏有问题(或破解补丁有问题),建议从别的网站上重新下载;第二,显卡驱动没装好(这种情况较多),建议重装显卡驱动;第三,硬件配置不够。 问题9:出现DirectDraw、Direct3D、AGP纹理加速不可用,修复后仍不能解决? 答:本程序的V3.2版本之后新增了一个开启该加速的功能,请先尝试使用该功能进行修复。如果修复后仍不能解决,则通常是由于显卡驱动有问题造成的,建议到显卡官网下载最新驱动安装即可(如显卡驱动异常,将会在开启DirectX加速页面右上角进行提示,仅限V3.9版或更高版本支持此功能)。 问题10:本程序是只能修复C盘中的DirectX吗?其他盘中的如何修复? 答:本程序不是只能修复C盘中的DirectX,而是修复当前系统所在磁盘的DirectX。如果您的操作系统安装在了C盘,则程序会修复C盘中的DirectX,如果您的操作系统安装在了D盘,则

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值