【问题标题】:Aren't template class member functions compiled at instantiation?模板类成员函数不是在实例化时编译的吗?
【发布时间】:2011-03-16 13:01:44
【问题描述】:

在将代码从 Visual Studio 移植到 gcc 时,我发现了一个奇怪的问题。以下代码在 Visual Studio 中编译良好,但在 gcc 中导致错误。

namespace Baz
{
   template <class T>
   class Foo
   {
   public:
      void Bar()
      {
         Baz::Print();
      }
   }; 

   void Print() { std::cout << "Hello, world!" << std::endl; }
}

int main()
{
   Baz::Foo<int> foo;
   foo.Bar();

   return 0;
}

我的理解是这应该可以编译,因为在模板实例化之前(在定义 Print() 之后)不应该编译该类。但是,gcc 报告以下内容:

t.cpp:在成员函数 'void Baz::Foo::Bar()' 中: 第 8 行:错误:'Print' 不是 'Baz' 的成员

谁是对的?如果 gcc 是正确的,为什么?

【问题讨论】:

  • 这是完整的代码吗?我认为您在类声明后缺少分号
  • 抱歉,打错了。那里有分号是正确的。
  • 我在测试编译器更新时遇到了类似的情况:stackoverflow.com/questions/1615660/…

标签: c++ visual-studio gcc


【解决方案1】:

gcc 是对的。模板分两个阶段编译:一次是在解析时,一次是在实例化时。

在解析时,任何不依赖于模板参数的东西都会被检查,所以在这种情况下Baz::Print被查找,发现没有被声明,所以这是一个错误。

如果调用是 T().Print() 或其他依赖于 T 类型的调用,则查找将延迟到实例化时间(或至少部分延迟 --- 见下文。)

像 Baz::Print 这样的限定名称总是在定义时查找,除非限定本身取决于模板参数(例如 T::Print),尽管重载解决方案推迟到实例化时间。这意味着您不能在声明模板后添加到限定名称的重载集。

如果任何函数参数依赖于模板参数,则在实例化时会查找诸如 Print 之类的非限定名称。所以Print(T()) 会在实例化时被查找,而Baz::Print(T()) 不会。值得注意的是,实例化时间查找仅限于那些在声明点可见的名称以及通过 ADL 找到的名称,因此对于Foo&lt;int&gt;,即使是普通的Print(T()),如果它在模板(如示例中)。

为了使示例代码正常工作,可以在Print 的定义之后定义Foo&lt;T&gt;::Bar,或者在Foo 的定义之前转发声明Print

【讨论】:

  • 奇怪,这里似乎是真的...codepad.org/eIHsc8Lu这可能是可变参数的错误吗? (因为在这种情况下,类型无关紧要。)
  • @shi:您的示例未使用命名空间 (Baz::...) 限定调用。
  • @Shirik:恕我直言,这是非标准行为。请参阅我在回复中添加的标准引用。
  • 我已经扩展了我的答案以包括来自这些 cmets 的点。
【解决方案2】:

gcc 是对的。这是因为Baz 是一个命名空间,并且命名空间是从上到下解析的,所以Baz::Print 的声明在Foo 内部是不可见的(因为它在它的下方)。

当模板被实例化时,只考虑模板定义中可见的名称,不包括 Koenig 查找(在您的情况下不会改变任何内容)。

如果Baz 是一个结构或类,您的代码将起作用,因为它们分两个阶段进行解析(首先是声明,然后是主体),因此在结构或类中声明的任何内容在例如内部可见。成员函数,无论它们在源文件中的顺序如何。

您可以通过在Foo 之前声明Baz::Print 来使其工作。

引用标准:

14.6.3 非依赖名称

模板定义中使用的非依赖名称使用通常的名称查找找到并绑定在 点它们被使用。

14.6.4 从属名称解析

在解析从属名称时,会考虑来自以下来源的名称:

  • 在模板定义处可见的声明。
  • 来自与函数参数类型相关的命名空间的声明 来自实例化上下文 (14.6.4.1) 和定义上下文。

(结束引用)

Print 不依赖时(就像现在一样),它不会被发现,因为它是在其声明之前查找的(在模板定义上下文中)。如果它是依赖的,它就不是第一种情况(与非依赖时相同),并且Baz与int(模板参数)没有任何关联,因此不会根据第二种情况进行搜索要么。

【讨论】:

  • Also... 14.6.4.2 对于依赖于模板参数的函数调用,如果函数名是非限定 ID 或使用运算符表示法调用函数,则找到候选函数使用通常的查找规则 (3.4.1, 3.4.2),除了 (1) 对于使用非限定名称查找 (3.4.1) 的查找部分,仅找到来自模板定义上下文的函数声明。 (2) 对于使用关联命名空间 (3.4.2) 的查找部分,仅找到在模板定义上下文或模板实例化上下文中找到的函数声明。
  • @jpalececk,感谢所有边缘情况的详细回答(特别是关于非限定名称和限定名称)。这是一个非常有趣的情况。
  • @shi: Baz::Print 是一个合格的名字,而不是一个不合格的名字。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-09
  • 2013-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多