栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++ 使用typename来修复模板编译错误

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++ 使用typename来修复模板编译错误

问题由来

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

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



template  using TMethodPtr      = typename TMemFunPtrType::Type;

疑惑的点在于,下面这一块代码是作为函数签名里的一个参数出现的:

typename FInputAxisHandlerSignature::TMethodPtr< UserClass >

因为在我之前的理解,typename是只能用于template的尖括号里的,所以写下这篇文章研究一下。

参考: Why typename?

使用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。至于具体怎么改,警告其实已经告诉我了,改成:

template
struct TemplateExample 
{
	T m_T;
	typename T::E m_Empty;
};

所以核心原因是,编译器会默认认为Template的scope里面(即花括号里的内容)的所有出现的identifier是value,而不是类型(可以认为任何人为命名的东西都是identifier)。这个问题还有别的变种:

看这种情况,当编译器碰到Remains时,它默认认为这是变量,那么就会报错,应该是因为变量后面不可以接,这里加个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
struct 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::TMethodPtr Func)
{
	...
}

根据:

template  using 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 TDelegate DelegateName;
	

// 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 TDelegate FInputAxisHandlerSignature;

也就是说,FInputAxisHandlerSignature是 TDelegate类型的typedef,所以FInputAxisHandlerSignature::TMethodPtr 相当于:

TDelegate TMethodPtr

展开TMethodPtr

继续往上展开,难点在于这里TMethodPtr类里的Type是什么类型

TDelegate TMemFunPtrType::Type

// 而这里的Type是在TMemFunPtrType类里定义的函数指针
template 
struct TMemFunPtrType
{
	// Type是个函数指针,  Class::*是意思难道必须是Class内部的成员函数的指针?
	typedef RetType (Class::* Type)(ArgTypes...);
};

所以最后的函数签名应该是:

TDelegate AMyCharacter::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::Type)': expects 3 arguments - 2 provided
2>E:UE_5.0UE_5.0EngineSourceRuntimeEngineClassesComponentsInputComponent.h(906): note: see declaration of 'UInputComponent::BindAxis'
暑期编程PK赛 得CSDN机械键盘等精美礼品!
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1015117.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号