为什么函数模板专用化是不同的,尽管返回类型或参数没有区别?

Why are function template specializations distinct, despite no difference in return type or parameters?

提问人:Blair Fonville 提问时间:8/13/2023 最后编辑:Jan SchultkeBlair Fonville 更新时间:8/13/2023 访问量:87

问:

为什么以下代码有效?

#include <iostream>
#include <cstdint>

template <class D>
void whichD() {
    std::cout << "D is " << sizeof(D) << " byte(s)\n";
}

int main(int argc, char **argv)
{
    if (argv[1][0] == '1') {
        whichD<uint8_t>();
    }
    else {
        whichD<uint16_t>();
    }
}

程序输出./temp 1 && ./temp 2
D is 1 byte(s)
D is 2 byte(s)

我没想到它会起作用,原因与你不能拥有的原因相同:

void foo() { /* ... */ } 
void foo() { /* ... */ } // error: re-definition

int bar() { /* ... */ }
double bar() { /* ... */ } // error: re-definition
C++ 模板 language-lawyer

评论

3赞 alfC 8/13/2023
因为如果指定了模板参数,编译器就知道要实例化和调用的模板。用更花哨的术语来说:因为是一个函数,但是一个函数模板。youtube.com/watch?v=NIDEjY5ywqUfoowhichD
0赞 Blair Fonville 8/13/2023
谢谢你@alfC。但是所有代码最终都需要归结为完全指定的类型,对吧?在某些时候,编译器需要对完全定义的代码进行操作。多年来,我写了很多无效的代码,试图只用模板化返回类型(即没有唯一的函数参数)来编写函数——只是发现你不能这样做。所以。。一路上我一定错过了一个微妙之处。我怀疑这与翻译单位有关。
0赞 Blair Fonville 8/13/2023
@alfC,我会看链接的。谢谢你。
0赞 Paul Sanders 8/13/2023
whichD<uint8_t>和是两种不同的(且完全指定的)类型。或者最好说,两个不同的功能有两个不同的名称。提供模板参数后,您将获得一个具体类型。whichD<uint16_t>
0赞 alfC 8/13/2023
没错,是一个函数,是一个函数模板,而 和 又回到了编译器生成的函数。它们可能成为具体的代码符号(称它们为 和 )。由于这两个函数可以在语法上区分,因此编译器可以毫无问题地允许它。这就是模板的力量。foowhichDwhichD<int8>whichD<int16>whichD_int8whichD_int16

答:

0赞 azhen7 8/13/2023 #1

编译器根据模板参数进行区分。

在 的示例中,如果您调用 ,编译器无法推断出调用了哪个函数重载,因此会引发错误。foofoo()

这里,是相同的函数,但模板参数不同。在编译过程中的某个时候,这些函数调用将被转换为符号(例如 和分别,或类似的东西)。 和是两个不同的函数名称,所以没有歧义。whichD<uint8_t>whichD<uint16_t>whichD_uint8_twhichD_uint16_twhichD_uint8_twhichD_uint16_t

2赞 Jan Schultke 8/13/2023 #2

的确,函数必须具有不同的签名。 否则,它们是彼此的重新定义。 但是,签名的构成取决于实体的种类。

非模板函数的签名

对于非模板函数,签名定义为:

签名

⟨function⟩名称、parameter-type-list 和封闭命名空间

- [defns.签名]

例如:

  • void foo()并具有不同的签名,因为它们具有不同的名称void bar()
  • void foo(int)并具有不同的参数类型列表void foo(float)
  • void a::foo()并具有不同的封闭命名空间void b::foo()
  • void foo()并且是彼此的重新定义;返回类型不是签名的一部分,因此这些函数不是不同的。int foo()

函数模板专用化的签名

具有给定模板参数(例如)的函数模板(例如)称为函数模板专用化函数模板专用化的特征是:whichDwhichD<uint8_t>

签名

⟨函数模板专用化⟩它所特化的模板的签名及其模板参数(无论是显式指定还是推导)

- [defns.signature.templ.spec]

在您的示例中,是同一函数模板的专用化,但它们的模板参数不同,使它们成为不同的函数。whichD<uint8_t>whichD<uint16_t>

编译器不只是放弃所有的模板性并使用模板来生成:

void whichD() { /* ... */ }
void whichD() { /* ... */ } // error: re-definition

相反,编译器将实例化这些专用化、包含的参数,并生成如下内容:

void whichD<uint8_t>() { /* ... */ }
void whichD<uint16_t>() { /* ... */ } // OK, different from above

这两个专业化的签名会被破坏,因此(以某种破坏的形式)包含在链接器看到的符号中。uint8_tuint16_t

这些规则背后的原因

请记住,这些规则的存在是为了区分可以单独调用函数的情况,以及不能单独调用函数的情况。如果 和 were 不是专用化,而是带有名称的常规函数,您将无法调用两者之一。 过载分辨率无法区分它们。whichD<uint8_t>whichD<uint16_>whichD

但是,正如您在代码中演示的那样,通过显式提供模板参数,可以很容易地单独调用和调用。 如果说这两者具有相同的签名,那将是一种武断的限制。whichD<uint8_t>whichD<uint16_t>