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