【问题标题】:Template argument deduction from function body从函数体中推导模板参数
【发布时间】:2011-06-15 12:01:12
【问题描述】:

如果我们有这个函数模板,

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

基本上有两个问题:

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

我想知道你对这种扣除的看法。


编辑:

顺便说一句,如果我们编写这段代码,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;
}

此示例的工作演示:http://www.ideone.com/cvXEA

上一个示例的演示无效:http://www.ideone.com/UX038

【问题讨论】:

    标签: c++ templates standards iso


    【解决方案1】:

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

    模板匹配算法只考虑函数签名,而不考虑它们的主体。虽然不经常使用,但在不提供主体的情况下简单地声明模板化函数是完全合法的:

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

    为了达到同样的效果。


    关于您在编辑后添加的示例:虽然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;
    }
    

    那么编译器在第 1 阶段仍会认为 g() 是一个有效匹配项。当发现 sample 没有名为 NOT_A_member 的成员时,您会收到错误消息。

    【讨论】:

    • BOOST_AUTO(v, param.member)
    • 如果sample是第三方代码你无法更改,你仍然可以使用Traits来达到同样的效果。然后你可以做Traits&lt;Targ&gt;::MemberType v = param.member;
    • 更新问题。但基本上,您是在以这种方式协助编译器。并且:如果g(T p) 中的参数p 不包含名为member 的成员,那么您将收到编译器错误。它仍然会匹配 g(T p)!
    • @Daniel:是的,是吗?结论是什么?
    • @Nawaz - 您更新后的问题是什么?第二个示例对应于您的原始问题,因此无法编译。第一个例子不需要编译器在匹配g()的时候推导出TBody,所以应该可以编译。
    【解决方案2】:

    编译器不可能对您提供的代码做几件事,其中第一件事是推导出第二个模板参数TBody。首先,类型推导仅在编译器尝试匹配调用时应用于函数的参数。那时甚至没有查看模板化函数的定义。

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

    现在,在第二个代码块上。为了理解它,当编译器在调用点看到函数调用g(x) 时,模板编译的整个过程就开始了。编译器发现最佳候选者是模板函数template &lt;typename T&gt; void g( T ),并确定T 的类型是重载决议的一部分。一旦编译器确定这是对模板的调用,它就会对该函数执行第一遍编译。

    在第一轮中,语法检查是在没有实际替换类型的情况下执行的,所以模板参数 T 仍然是 any-type 而参数 p 是一个尚未 -未知类型。在第一次通过时,代码被验证,但依赖名称被跳过,它们的含义只是假设。当编译器看到p.member,并且pT 类型时,它是一个模板参数,它假定它将是一个未知类型的成员(这就是为什么如果它是一个类型你会有在此处使用typename 对其进行限定)。调用 h(p.member); 也依赖于类型参数 T 并保持原样,假设一旦发生类型替换,一切都会有意义。

    然后编译器确实会替换类型。在这一步T不再是一个generic类型,而是代表了具体类型sample。现在编译器在第二遍期间尝试填补在第一遍期间留下的空白。当它看到p.member 时,它会在类型内部查找member 并确定它是int 并尝试使用该知识解析调用h( p.member );。因为类型 T 在第二阶段之前已经解析,这相当于外部调用 g(x):所有类型都是已知的,编译器只需要解析函数调用 h 的最佳重载int&amp; 类型的参数,整个过程重新开始,模板h 被找到为最佳候选,并且...

    对于元编程来说,理解类型推导只对函数的实际签名而不是函数体执行是非常重要的,这对初学者来说并不重要。在函数签名中使用 enable_if(来自 boost 或其他地方)作为参数或返回类型并非巧合,但唯一的方法是让编译器无法替换类型 before 模板被选为最佳候选,替换失败变成实际错误(而不是 SFINAE)

    【讨论】:

      【解决方案3】:

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

      所以在某些极端情况下,理论上可以推导出TBody 的正确类型,但通常不是。

      【讨论】:

        【解决方案4】:

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

        【讨论】:

        • 您能详细说明一下吗?
        猜你喜欢
        • 1970-01-01
        • 2018-12-05
        • 2021-08-31
        • 2015-09-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-01
        • 1970-01-01
        相关资源
        最近更新 更多