简体   繁体   English

为什么这个模板函数没有按预期运行?

[英]Why does this template function not behave as expected?

I was reading about template functions and got confused by this problem:我正在阅读有关模板函数的内容,并对这个问题感到困惑:

#include <iostream>

void f(int) {
    std::cout << "f(int)\n";
}

template<typename T>
void g(T val) {
    std::cout << typeid(val).name() << "  ";
    f(val);
}

void f(double) {
    std::cout << "f(double)\n";
}

template void g<double>(double);

int main() {
    f(1.0); // f(double)
    f(1);   // f(int)
    g(1.0); // d  f(int), this is surprising
    g(1);   // i  f(int)
}

The results are the same if I don't write template void g<double>(double);如果我不写template void g<double>(double);结果是一样的. .

I think g<double> should be instantiated after f(double) , and therefore the call to f in g should call f(double) .我认为g<double>应该在f(double)之后实例化,因此在g中对f的调用应该调用f(double) Surprisingly, it still calls f(int) in g<double> .令人惊讶的是,它仍然在g<double>中调用f(int) Can anyone help me understand this?谁能帮我理解这个?


After reading the answers, I figured out what my confusion really is.阅读答案后,我弄清楚了我的困惑到底是什么。

Here is an updated example.这是一个更新的例子。 It is mostly unchanged except that I added a specialization for g<double> :除了我为g<double>添加了一个专业化之外,它几乎没有变化:

#include <iostream>

void f(int){cout << "f(int)" << endl;}

template<typename T>
void g(T val)
{
    cout << typeid(val).name() << "  ";
    f(val);
}

void f(double){cout << "f(double)" << endl;}

//Now use user specialization to replace
//template void g<double>(double);

template<>
void g<double>(double val)
{
    cout << typeid(val).name() << "  ";
    f(val);
}

int main() {
    f(1.0); // f(double)
    f(1);  // f(int)
    g(1.0); // now d  f(double)
    g(1);  // i  f(int)
}

With the user specialization, g(1.0) behaves as I expected.通过用户专业化, g(1.0)的行为符合我的预期。

Should the compiler not automatically do this same instantiation for g<double> in the same place (or even after main() , as described in section 26.3.3 of The C++ Programming Language , 4th edition)?编译器是否应该在同一个地方(或者甚至在main()之后,如《C++ 编程语言》第 4 版第 26.3.3 节中所述)自动为g<double>执行相同的实例化?

The name f is a dependent name (it depends on T via the argument val ) and it will be resolved into two steps :名称f是一个从属名称(它通过参数val依赖于T ),它将被解析为两个步骤

  1. Non-ADL lookup examines function declarations... that are visible from the template definition context .非 ADL 查找检查函数声明...从模板定义上下文中可见。
  2. ADL examines function declarations... that are visible from either the template definition context or the template instantiation context . ADL 检查函数声明...从模板定义上下文模板实例化上下文可见。

void f(double) is not visible from the template definition context, and ADL will not find it either, because void f(double)在模板定义上下文中不可见,ADL 也找不到它,因为

For arguments of fundamental type, the associated set of namespaces and classes is empty对于基本类型的参数,关联的命名空间和类集为空


We can slightly modify your example:我们可以稍微修改您的示例:

struct Int {};
struct Double : Int {};

void f(Int) { 
    std::cout << "f(Int)";
}

template<typename T>
void g(T val) {
    std::cout << typeid(val).name() << ' ';
    f(val);
    // (f)(val);
}

void f(Double) { 
    std::cout << "f(Double)";
}

int main() {
    g(Double{});
}

Now ADL will find void f(Double) in the second step, and the output will be 6Double f(Double) .现在 ADL 将在第二步中找到void f(Double) ,输出将是6Double f(Double) We can disable ADL by writing (f)(val) (or ::f(val) ) instead of f(val) .我们可以通过编写(f)(val) (或::f(val) )而不是f(val)来禁用 ADL。 Then the output will be 6Double f(Int) , in agreement with your example.然后输出将是6Double f(Int) ,与您的示例一致。

The problem is f(double) has not been declared at the point where you call it;问题是f(double)没有在你调用它的地方声明; if you move its declaration in front of the template g , it will get called.如果你将它的声明移到template g的前面,它就会被调用。

Edit: Why would one use manual instantiation?编辑:为什么要使用手动实例化?

(I'll talk about function templates only, analogous argumentation holds for class templates too.) The main use is to reduce compilation times and/or to hide the code of the template from users. (我只讨论函数模板,类似的论证也适用于类模板。)主要用途是减少编译时间和/或对用户隐藏模板代码。

C++ program are built into binaries in 2 steps: compilation and linking. C++ 程序通过 2 个步骤构建为二进制文件:编译和链接。 For the compilation of a function call to succeed only the header of the function is needed.为了编译成功的函数调用,只需要函数的头文件。 For the linking to succeed, an object file containing compiled body of the function is needed.为使链接成功,需要一个包含已编译函数体的目标文件。

Now when the compiler sees a call of a templated function, what it does depends on whether it knows the body of the template or only the header.现在,当编译器看到模板函数的调用时,它会做什么取决于它是知道模板的主体还是只知道标题。 If it only sees the header it does the same thing as if the function was not templated: puts information about the call for the linker to the object file.如果它只看到标头,它会做与函数未模板化相同的事情:将有关链接器调用的信息放入目标文件。 But if it also sees the body of the template it does also another thing: it instantiates proper instance of the body, compiles this body and puts it into the object file as well.但是如果它也看到模板的主体,它还会做另一件事:它实例化主体的适当实例,编译这个主体并将它也放入目标文件中。

If several source files call the same instance of the templated function, each of their object files will contain a compiled version of the instance of the function.如果多个源文件调用模板函数的同一个实例,则它们的每个目标文件都将包含该函数实例的编译版本。 (Linker knows about this and resolves all the calls to a single compiled function, so there will only be one in the final binary of the program/library.) However in order to compile each of the source files the function had to be instantiated and compiled, which took time. (链接器知道这一点并将所有调用解析为单个编译函数,因此程序/库的最终二进制文件中只有一个。)但是为了编译每个源文件,必须实例化该函数并编译,这需要时间。

It's enough for the linker to do it's job if the body of the function is in one object file.如果函数体在一个目标文件中,链接器就可以完成它的工作。 To manually instantiate the template in a source file is a way to make the compiler put the body of the function into the object file of the source file in question.在源文件中手动实例化模板是一种使编译器将函数体放入相关源文件的目标文件中的方法。 (It's kinda as if the function were called, but the instantiation is written in a place where function call would be invalid.) When this is done, all the files that call your function can be compiled knowing only the header of the function, thus saving time it would take to instantiate and compile the body of the function with each of the calls. (这有点像函数被调用,但是实例化写在函数调用无效的地方。)完成后,所有调用函数的文件都可以在只知道函数头的情况下编译,因此节省了每次调用时实例化和编译函数体所需的时间。

The second reason (implementation hiding) might make sense now.第二个原因(实现隐藏)现在可能有意义。 If a library author wants users of her template function to be able to use the function, she usually gives them the code of the template, so they can compile it themselves.如果库作者希望她的模板函数的用户能够使用该函数,她通常会把模板的代码给他们,这样他们就可以自己编译了。 If she wanted to keep the source code of the template secret she could manually instantiate the template in the code she uses to build the library and give the users the object version thus obtained instead of the source.如果她想对模板的源代码保密,她可以在她用来构建库的代码中手动实例化模板,并向用户提供由此获得的对象版本而不是源代码。

Does this make any sense?这有意义吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM