在哪个位置发生模板实例化绑定?

时间:2016-10-10 13:09:17

标签: c++ templates c++14 name-binding template-instantiation

此代码来自" C ++编程语言"作者:Bjarne Stroustrup(C.13.8.3实例化绑定点)

template <class T>
void f(T value)
{
    g(value);
}

void g(int v);

void h()
{
    extern g(double);
    f(2);
}

他提到:

  

这里,f()的实例化点恰好在h()之前,所以   f()中调用的g()是全局g(int)而不是本地   克(双)。 “实例化点”的定义意味着a   模板参数永远不能绑定到本地名称或类   构件。

void h()
{
    struct X {}; // local structure
    std::vector<X> v; // error: can't use local structure as template parameter
}

我的问题是:

  1. 为什么第一个代码有效? g()稍后被声明,我真的得到了G ++ 4.9.2的错误,g当时没有声明。

  2. extern g(double) - 这是如何运作的?因为在函数重载的情况下返回值并不重要,那么我们可以在前向声明中错过它吗?

  3. f()的实例化点就在h()之前 - 为什么?在调用f(2)时它会被实例化是不合逻辑的?就在我们称之为的地方,g(double)已经在范围内了。

  4. ''实例化点'的定义意味着模板参数永远不能绑定到本地名称或类成员 - 这在C ++ 14中有变化吗?我在使用C ++时遇到错误(G ++ 4.9.2),但在C ++ 14(G ++ 4.9.2)中没有错误。

1 个答案:

答案 0 :(得分:15)

“1985年,第一版的C ++编程语言发布,成为该语言的权威参考,因为还没有官方标准。” wiki C++ History因此它在C ++ 11和C ++ 14之间没有变化。我可以假设(并且请带着一点点盐)它在“预标准化”和标准化之间发生了变化。也许更了解C ++历史的人可以在这里发挥更多的作用。

至于实际发生的事情:

首先让我们摆脱简单的方式:

extern g(double);

这是无效的C ++。从历史上看,不幸的是C允许遗漏类型。在C ++中,您必须编写extern void g(double)

接下来,让我们忽略g(double)重载来回答您的第一个问题:

template <class T>
void f(T value)
{
    g(value);
}

void g(int v);

int main()
{
    f(2);
}

在C ++中有臭名昭着的两阶段名称查找:

  • 在第一阶段,在模板定义中,解析了所有non-dependent names。不这样做是一个很难的错误;
  • 在模板实例化的第二阶段解析从属名称。

规则有点复杂,但这就是它的要点。

g依赖于模板参数T,因此它会通过第一阶段。这意味着如果您从未实例化f,则代码编译得很好。在第二阶段f使用T = int进行实例化。现在搜索g(int),但未找到:

17 : error: call to function 'g' that is neither visible in the template definition nor found by argument-dependent lookup
g(value);
^
24 : note: in instantiation of function template specialization 'f<int>' requested here
f(2);
^
20 : note: 'g' should be declared prior to the call site
void g(int v);

为了让任意名称g通过飞扬的颜色,我们有几个选择:

  1. 先前声明g
  2. void g(int);
    
    template <class T>
    void f(T value)
    {
        g(value);
    }
    
    1. g带入T
    2. template <class T>
      void f(T)
      {
          T::g();
      }
      
      struct X {
         static void g();
      };
      
      int main()
      {
          X x;
          f(x);
      }
      
      1. 通过ADL带来g T
      2. template <class T>
        void f(T value)
        {
            g(value);
        }
        
        struct X {};
        
        void g(X);
        
        int main()
        {
            X x;
            f(x);
        }
        

        这些当然会改变程序的语义。它们旨在说明您在模板中能够和不能拥有的内容。

        至于为什么ADL找不到g(int),但找到g(X)

          

        §3.4.2依赖于参数的名称查找[basic.lookup.argdep]

             
            
        1. 对于函数调用中的每个参数类型T,有一组零个或多个关联的命名空间以及一组零或多个   要考虑的相关类别:[...]:

               
              
          • 如果T是基本类型,则其关联的命名空间和类集都是空的。

          •   
          • 如果T是类类型(包括联合),则其关联的类是:类本身;它所属的成员,如果有的话;和   它的直接和间接基类。它的相关命名空间是   其关联类是成员的名称空间。 [...]

          •   
        2.   

        最后我们了解为什么找不到main中的extern void g(double);:首先我们发现g(fundamental_type)被找到iff它是在f定义之前声明的。所以我们在void g(X)内设main。 ADL能找到吗?

        template <class T>
        void f(T value)
        {
            g(value);
        }
        
        struct X{};
        
        
        int main()
        {
          X x;
          void g(X);
        
          f(x);
        }
        

        没有。因为它不与X(即全局命名空间)位于同一名称空间中,所以ADL找不到它。

        证明g不在全球

        int main()
        {
          void g(X);
        
          X x;
          g(x); // OK
          ::g(x); // ERROR
        }
        
          

        34:错误:全局命名空间中没有名为'g'的成员;你的意思是   只是'g'?