2011-01-25 120 views
3

如果我们在这一函数模板,模板参数推导

template<typename T> 
void f(T param) {} 

然后我们就可以把它在以下几个方面,

int i=0; 
f<int>(i);//T=int : no need to deduce T 
f(i); //T=int : deduced T from the function argument! 

//likewise 
sample s; 
f(s); //T=sample : deduced T from the function argument! 

现在考虑上述函数模板的这个变体,

template<typename TArg, typename TBody> 
void g(TArg param) 
{ 
    TBody v=param.member; 
} 

现在,可以在编译器推断模板参数,如果我们写,

sample s; 
g(s); //TArg=sample, TBody=int?? 

假设sample被定义为,

struct sample 
{ 
    int member; 
}; 

基本上有两个问题:

  • 编译器可以推断出在第二个例子中的模板参数?
  • 如果不是,那为什么?有什么困难吗?如果标准没有说出“从函数体”“模板参数推导的任何内容,那么是不是因为参数不能推导出来?或者它没有考虑这样的推论,以避免增加语言的复杂性?或者是什么?

我想知道你对这样的推论的看法。


编辑:

顺便说,海湾合作委员会是能够推断出函数的参数,如果我们写这样的代码:http://www.ideone.com/cvXEA

不:

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; 
} 

在这个例子中工作示范上例的工作演示:http://www.ideone.com/UX038

回答

0

没有编译器可能以一致的方式实现此功能。你只是要求太多。

+0

请你详细说明一下吗? – Nawaz 2011-01-25 21:50:32

0

TBody可能不明确,因为sample可能不是唯一具有member成员的类型。此外,如果g调用其他模板函数,编译器无法知道可能对TBody施加了什么其他限制。

因此,在某些边缘情况下,理论上可以推导出TBody的正确类型,通常情况下不可以。

5

您可能已经得出结论,编译器将不会通过检查sample.member的类型推断出TBody。这会给模板演绎算法增加另一个复杂程度。

模板匹配算法只考虑功能签名,而不是他们的身体。虽然没有使用过于频繁,这是完全合法的,只是声明了一个模板功能,而不提供身体:

template <typename T> void f(T param); 

这满足编译器。为了满足链接器,当然你也必须在某处定义函数体,并确保提供了所有必需的实例。但函数体确实不是而是必须对模板函数的客户端代码可见,只要所需的实例在链接时可用即可。该机构将必须显式实例化功能,如:

template <> void f(int param); 

但这只能部分适用于您的问题,因为你可以想像这样的以下内容,其中第2个参数可以从所提供的默认推导出一个场景参数,并且将未编译:

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); 

,以获得相同的效果。


关于你编辑后加入您的样品:而似乎h(p.member)不依赖于结构的部件上,因此模板匹配算法失效,它不会因为你来了两步过程:

  1. 见状g(s);,编译器会采取sample类型的参数中的任何功能(模板或不!)。在你的情况下,最好的匹配是void g(T p)至此,编译器甚至没有看过g(T p)的身体呢!
  2. 现在,编译器创建一个g(T p)的实例,专用于T: sample。因此,当它看到h(p.member)它知道p.member类型为int,并且将尝试找到一个函数h()采用int类型的参数。你的模板功能h(T p)原来是最好的匹配。

需要注意的是,如果你写了(注意NOT_A_member):

template<typename T> 
void g(T p) 
{ 
     h(p.NOT_A_member); 
     return; 
} 

那么编译器仍然会考虑在g()阶段1有效的匹配,那么你得到一个错误事实证明,当那sample没有一个叫NOT_A_member的成员。

+0

或`BOOST_AUTO(v,param.member)` – Anycorn 2011-01-25 21:39:28

0

编译器不可能对您提供的代码执行一些操作,其中第一项是推导出第二个模板参数TBody。首先,类型推导仅适用于编译器试图匹配调用时函数的参数。此时甚至没有看到模板化函数的定义。

对于额外的功劳,即使编译器要查看函数定义,代码TBody v = parameter.member本身也是不可推导的,因为有潜在的无限数据类型可以在构造函数中接受parameter.member

现在,在第二块代码上。为了理解它,模板编译的整个过程在编译器在该位置看到函数调用g(x)时开始。编译器认为最好的候选者是模板函数template <typename T> void g(T),并确定类型T是什么作为重载解析的一部分。一旦编译器确定它是对模板的调用,它将对该函数执行第一次编译。

在第一遍期间,句法检查是不带类型的实际替代执行的,所以模板参数T仍然任何型和参数p是一个尚未未知类型的。在第一次通过期间,代码被验证,但是依赖于名称被跳过,它们的含义只是假设。当编译器看到p.member,pT这是一个模板参数时,它假定它将是尚未知类型的成员(这就是为什么如果它是一种类型,您将不得不在此处使用typename )。调用h(p.member);也依赖于类型参数T并保持原样,假设一旦发生类型替换,所有事情都会有意义。

然后编译器确实替代了类型。在此步骤T不再是类型类型,但它代表具体类型sample。现在编译器在第二遍时尝试填写第一遍时留下的空白。当它看到p.member它看起来类型内member,并确定它是一个int,并尝试用该知识解决呼叫h(p.member);。因为T类型在第二阶段之前已经解决,所以这相当于外部调用g(x):所有类型都是已知的,并且编译器只需要解析函数调用h的最佳过载,该函数采用int&类型的参数,并且整个过程再次开始,模板h被发现是最佳候选者,并且...

元编程明白类型演绎仅在函数的实际签名而不是主体上执行是非常重要的,这对于初学者来说并不重要。在函数签名中使用enable_if(来自boost或其他地方)作为参数或返回类型不是巧合,但是使编译器无法替换之前的类型的唯一方法是将模板选择为最佳候选并且替换失败为变成了一个实际的错误(而不是SFINAE)