Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);.

I think g<double> should be instantiated after f(double), and therefore the call to f in g should call f(double). Surprisingly, it still calls f(int) in g<double>. 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>:

#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.

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)?

like image 377
Zhongqi Cheng Avatar asked Nov 29 '19 06:11

Zhongqi Cheng


2 Answers

The name f is a dependent name (it depends on T via the argument val) and it will be resolved into two steps:

  1. Non-ADL lookup examines function declarations ... that are visible from the template definition context.
  2. ADL examines function declarations ... that are visible from either the template definition context or the template instantiation context.

void f(double) is not visible from the template definition context, and ADL will not find it either, because

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). We can disable ADL by writing (f)(val) (or ::f(val)) instead of f(val). Then the output will be 6Double f(Int), in agreement with your example.

like image 60
Evg Avatar answered Nov 17 '22 06:11

Evg


The problem is f(double) has not been declared at the point where you call it; if you move its declaration in front of the template g, it will get called.

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. 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?

like image 6
AshleyWilkes Avatar answered Nov 17 '22 06:11

AshleyWilkes