Why does this template function not behave as expected?
Asked Answered
P

2

23

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

Polar answered 29/11, 2019 at 6:2 Comment(3)
The last call, g(1), gives i f(int) for me. You wrote d f(double). Was this a typo?Meridith
yes. sorry. updatedPolar
The basic principle of template is to support the use of operations on user types, while still preventing hijacking of internal library calls by user declared symbols. Which is an impossible compromise, as there is no "concept" contracts for templates, and it's too late to introduce such sound "contracts".Helse
P
12

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.

Parsimonious answered 29/11, 2019 at 6:47 Comment(5)
Thank you very much. I am wondering where the instantiation for g<double> is in the code. Is it just before main(). If so, shouldn't the instantiated g<double> definition be able to see both f(int) and f(double), and finally pick f(double)?Polar
@ZhongqiCheng At step 1 only the template definition context will be considered, and from that context void f(double) is not visible - this context ends before its declaration. At step 2 ADL will not find anything, so the template instantiation context doesn't play any role here.Parsimonious
@ZhongqiCheng, in your edit you introduced a definition after void f(double), so this function is visible from it. Now f is not a dependent name. If there were a better match for f(val); declared after the definition of g<double>, it will not be found, too. The only way to "look ahead" is ADL (or some old compiler that does not implement two-phase lookup correctly).Parsimonious
Here is my understanding of your answer. I should assume that the function templates (g<int> and g<double>) are instantiated right after the template definition. Therefore it wont see f(double). Is this correct. Thank you so much.Polar
@ZhongqiCheng, instantiated right before main(). They won't see f(double), because when instantiation happens, it is too late: phase one of lookup has already been done and it has found no f(double).Parsimonious
A
6

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?

Attired answered 29/11, 2019 at 6:10 Comment(7)
I woule be grateful if you can explain the difference between the instantiation presented in the first code of the author, and the specialization in the second code of the author after edit. I have read many times the site of cppreference about specialization and instantiation and books, but I did not understand. Thank youPrevaricate
@Dev: Please specify your question a bit more, I'm not sure what to answer. Basically in this case the difference is that when the specialization is present the compiler uses it, while when it is not present, the compiler takes the template, generates an instance of it and uses this generated instance. In the code above both the specialization and the instance of the template lead to the same code.Attired
My question is precisely abut that part of code : "template void g<double>(double);" It is named instantiation in programming template, if you know that. Specialization is a bit different, as it is declared like in the second code the author sent "template<> void g<double>(double val) { cout << typeid(val).name() << " "; f(val); }" Could you explain to me the difference ?Prevaricate
@Prevaricate I already tried to do that: the compiler uses a specialization if it can; if it can't see the specialization (e.g. because there is none) the compiler creates an instance of the template and uses that instance. In the code above both the template and the specialization lead to the same result, so the only difference is in what the compiler does to get to that result. In other cases the specialization could contain any implementation, it doesn't have to have anything in common with the template (but for the method header). Clearer?Attired
yes, but before he adds the specialization, there was only the "template void g<double>(double);" and i don't understand how it worksPrevaricate
The template void g<double>(double); is so called manual instantiation (note the template with no angle brackets, that's a distinguishing feature of the syntax); that tells the compiler to create an instance of the method. Here it has little effect, if it was not there, the compiler would generate the instance at the place the instance is called. The manual instantiation is rarely used feature, I'll say why you might want to use it after you confirm the thing is now clearer:-)Attired
The things begin to be clearer. I still need to understand how to use it. in which context, because it is rarely explained easilyPrevaricate

© 2022 - 2024 — McMap. All rights reserved.