【问题标题】:resolution of c++template specification and overloadc++模板规范和重载的解析
【发布时间】:2011-06-09 15:43:29
【问题描述】:

我已经阅读了Why Not Specialize Function Templates 并在进行了一些实验之后, 我发现了一件有趣的事情。这是main.cxx:

// main.cxx
#include <iostream>

// Declarations
/*
template<class T>
void foo(T);

template<>
void foo(int*);

template<class T>
void foo(T*);
*/

// Definition and specification
template<class T>
void foo(T x)
{
    std::cout << "T version." << std::endl;
}

template<>
void foo(int *i)
{
    std::cout << "int* version." << std::endl;
}

template<class T>
void foo(T *x)
{
    std::cout << "T* version" << std::endl;
}

int main(int argc, char** argv)
{
  int *p;
  foo(p);
}

有趣的是:如果我将声明部分保留为注释,则行为就像文章所说的那样,即如果 int* 版本的定义在其定义之前,则将使用 T* 版本,反之亦然。但是,如果取消注释声明块,无论我在定义或声明中使用哪种顺序,都只会调用 int* 版本。我的问题是该声明如何影响决议?

有什么想法吗?我在 x86_64-redhat-linux 上使用 g++ 4.2.2

编辑:在看到 AProgrammer 的回答后简化这个问题

【问题讨论】:

  • 不管重载决议如何,在单独的 .cpp 文件中定义模板是没有用的。调用者必须看到 i lt 才能实例化。
  • @n.m.,如果所有定义都去头文件,如果这个头文件也被另一个文件包含,会导致多定义的链接错误。
  • @ls 如果你内联它就不会
  • @ls 模板是一个(臭名昭著的)例外。
  • @AJG85 +1 用于内联解决方案:-)

标签: c++ templates template-specialization


【解决方案1】:

将源代码分发到三个文件中只会混淆问题:预处理使编译单元成为一个编译单元,其行为仅取决于 CU 的内容,而不是分发的文件数量。

我认为你对这种情况感到惊讶

#include <iostream>

template<class T> void foo(T); // A
template<> void foo(int*); // 1
template<class T> void foo(T*); // B

template<class T> void foo(T x)
{ std::cout << "T version." << std::endl; }

template<> void foo(int *i) // 2
{ std::cout << "int* version." << std::endl; }

template<class T> void foo(T *x)
{ std::cout << "T* version" << std::endl; }

int main(int argc, char** argv) {
  int *p;
  foo(p);
}

你会得到int* version。这是预期的行为。虽然 (1) 确实声明了 template &lt;typename T&gt; void foo(T) 的特化,但 (2) 不是该特化的定义。 (2) 定义并声明template&lt;class T&gt; void foo(T*); 的特化,然后在main() 中调用它。如果您在放置声明和定义的任何内容中给出三个定义之前的三个声明,则会发生这种情况。定义 (2) 将始终看到声明 template&lt;class T&gt; void foo(T*);,因此是它的特化。

当一个函数模板的特化被声明或定义并且它可以是多个函数模板的特化(如这里(2)可以是两个重载A和B的特化,它们只需要被声明),它是“更专业”的专业化。您可以在标准部分 17.5.5.2 中看到“更专业化”的精确定义,但很容易看出,对于 (2),B 比 A 更匹配,因此 (2) 是 (B) 的专业化. (1) 声明 (A) 的特化,因为当声明 (1) 时,还没有看到 (B)。如果你想在看到(B)之后给出(1)的定义,你必须写

template <> void foo<int*>(int*) // definition for (1)
{ std::cout << "foo<int*>(int*)\n"; }

定义 (2) 时也可以是显式的:

template<> void foo<int>(int *i) // 2 alternate
{ std::cout << "int* version." << std::endl; }

(但显然给 (2) 和同一个 CU 中的这个替代版本会给你一个错误)。

调用函数时也可以显式:

foo(p); // call (2)
foo<int>(p); // call (2)
foo<int*>(p); // call (1)

【讨论】:

  • @AProgrammer, (2) 也看到了 template void foo(T) 的声明,为什么不是它的规范呢?这里有什么潜规则吗?
  • @ls,规则隐藏在 14.5.5.2 下,占用的页面比另一页示例少一点。我不想在这里解释它们。
  • @AProgrammer,+1 供您确认我最初的假设,即声明“给函数模板解析提供一些见解”。这是我想要揭开的神秘部分,所以我编辑了我最初的问题。
  • @AProgrammer 首先对您的编号感到有些困惑:-)。我在这里总结一下,以防像我这样的人感到困惑:1)实际上是A)的声明规范; 2)是B)的定义规范。所以它没有为 1) 留下定义。为了添加 1) 的定义,应该使用包含 "//definition for (1)" 的代码块。之后,当实例化时,编译器只是按照文章所说的那样运行,即选择最佳的重载版本,即 B),它将选择其规范,即 2)。
  • @AProgrammer 继续我上次的评论...所以为了进一步证明这篇文章,如果我们添加“//definition for (1)”代码块并删除 //2,它将仍然调用“T* 版本”而不是刚刚添加的完整规范。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-04
  • 2011-11-14
  • 1970-01-01
  • 2016-10-23
相关资源
最近更新 更多