C++ 从函数体导出模板参数
如果我们有这个函数模板C++ 从函数体导出模板参数,c++,templates,standards,iso,C++,Templates,Standards,Iso,如果我们有这个函数模板 template<typename T> void f(T param) {} template<typename TArg, typename TBody> void g(TArg param) { TBody v=param.member; } 假设sample定义为 struct sample { int member; }; 基本上有两个问题: 编译器能否推断第二个示例中的模板参数 若否,原因为何?有什么困难吗?如果
template<typename T>
void f(T param) {}
template<typename TArg, typename TBody>
void g(TArg param)
{
TBody v=param.member;
}
假设sample
定义为
struct sample
{
int member;
};
基本上有两个问题:
- 编译器能否推断第二个示例中的模板参数
- 若否,原因为何?有什么困难吗?如果标准没有提到“从函数体中推导模板参数”,那么是因为无法推导参数吗?或者,它没有考虑这样的推导,以避免增加语言的复杂性?还是什么
编辑: 顺便说一下,如果我们编写以下代码,GCC能够推断函数参数:
template<typename T>
void h(T p)
{
cout << "g() " << p << endl;
return;
}
template<typename T>
void g(T p)
{
h(p.member); //if here GCC can deduce T for h(), then why not TBody in the previous example?
return;
}
模板
空位h(tp)
{
cout任何编译器都不可能以一致的方式实现此功能。您的要求太多了。TBody
可能不明确,因为sample
可能不是唯一具有成员的类型。此外,如果g
调用其他模板函数,编译器无法了解可能对t车身施加的其他限制
因此,在某些边缘情况下,理论上可以推断出TBody
的正确类型,通常情况下不是这样。您可能已经得出结论,编译器不会通过检查sample.member
的类型来推断TBody
。这将给模板推断算法增加另一个复杂度。
模板匹配算法只考虑函数签名,而不考虑函数体。虽然不经常使用,但在不提供函数体的情况下声明模板函数是完全合法的:
template <typename T> void f(T param);
编译器仍然会考虑<代码>()
阶段1中的有效匹配。当示例
没有名为非a_成员
的成员时,会出现一个错误。编译器可能无法对您提供的代码执行两件事,第一件事是推导出第二个模板参数TBody
。首先,键入只有当编译器试图匹配调用时,演绎才会应用于函数的参数。此时,甚至不会查看模板函数的定义
对于额外的积分,即使编译器要查看函数定义,代码TBody v=parameter.member
本身也是不可推断的,因为构造函数中可能有无限的数据类型可以接受parameter.member
现在,关于第二段代码。为了理解它,当编译器在调用点看到函数调用g(x)
时,模板编译的整个过程就开始了。编译器看到最好的候选函数是模板函数template void g(T)
并确定作为重载解析的一部分的类型T
。一旦编译器确定这是对模板的调用,它将对该函数执行第一次编译
在第一个过程中,语法检查在没有实际替换类型的情况下执行,因此模板参数T
仍然是任意类型,参数p
是未知类型。在第一个过程中,验证代码,但跳过依赖名,并假定其含义。当编译器看到p.member
,而p
的类型为T
,这是一个模板参数,它假设它将是未知类型的成员(这就是为什么如果它是一个类型,您必须在这里用typename
限定它的原因)。调用h(p.member);
也依赖于类型参数T
,并保持原样,假设一旦发生类型替换,一切都有意义
然后编译器确实替换了类型。在这一步中,T
不再是泛型类型,但它代表了具体的类型sample
。现在,编译器在第二遍中尝试填补第一遍中留下的空白。当它看到p.member
时,它看起来像member
键入并确定它是一个int
,并尝试使用该知识解析调用h(p.member);
。由于类型T
在第二阶段之前已解析,因此这相当于外部调用g(x)
:所有类型都是已知的,编译器只需解决函数调用h
的最佳重载问题,该函数调用的参数类型为int&
,整个过程再次开始,模板h
被发现为最佳候选,并且
对于元编程来说,理解类型推断只在函数的实际签名上执行而不是在主体上执行是非常重要的,这对于初学者来说是非常重要的。使用enable_if
(来自boost或其他地方)在函数中,签名作为参数或返回类型不是巧合,但在选择模板作为最佳候选者之前,编译器无法替换类型的唯一方法是将替换失败转化为实际错误(而不是SFINAE)或BOOST_AUTO(v,param.member)
如果sample
是您不能更改的第三方代码,您仍然可以使用Traits
来获得相同的效果。然后您可以执行Traits::MemberType v=param.member;
更新问题。但基本上,您是在以这种方式协助编译器。并且:如果g(tp)中的参数p
没有包含名为member
的成员,那么您将收到一个编译器错误。它仍然匹配g(tp)
!@Daniel:是的,那么?结论是什么?@Nawaz-您更新的问题的问题是什么?第二个示例对应于您的原始问题
template<typename T>
void h(T p)
{
cout << "g() " << p << endl;
return;
}
template<typename T>
void g(T p)
{
h(p.member); //if here GCC can deduce T for h(), then why not TBody in the previous example?
return;
}
template <typename T> void f(T param);
template <> void f(int param);
template<typename TArg, typename TBody>
void g(TArg param, TBody body = param.member); // won't deduce TBody from TArg
struct sample
{
typedef int MemberType;
MemberType member;
};
template<typename TArg>
void g(TArg param)
{
typename TArg::MemberType v = param.member;
}
sample s = { 0 };
g(s);
template<typename T>
void g(T p)
{
h(p.NOT_A_member);
return;
}