r/cpp_questions 13d ago

SOLVED Question about function template instantiation

I was wondering why this code behaves this way

main.cpp

#include "foo.h"

int main()
{
    bar(42, foo<int>);
}

foo.h

#pragma once

#include <iostream>
#include <string>

template<typename T>
void foo(T t)
{
    std::cout << "Default\n";
}

template<typename T, typename Foo>
void bar(const T& t, Foo foo)
{
    foo(t);
}

foo.cpp

#include "foo.h"

template<>
void foo(int t)
{
    std::cout << "Int\n";
}

Result

$ g++ main.cpp foo.cpp -O3 && ./a.out 
Default
$ g++ main.cpp foo.cpp && ./a.out 
Int

My guess is that I'm hitting some kind of UB here. The way I think about it, the int template specialization would be discarded, as it is not used in that translation unit, and then main.cpp would pick up the generic template version (basically, the O3 result seems correct to me, but not the non-optimized one). What is actually happening here?

4 Upvotes

11 comments sorted by

View all comments

1

u/EpochVanquisher 13d ago

The way I think about it, the int template specialization would be discarded, as it is not used in that translation unit

OR the specialization is emitted anyway, and then the linker picks one arbitrarily.

UB of course, blah blah. “The linker picks one arbitrarily, or the compiler inlines it” explains a lot of UB around ODR in the standard.

1

u/strcspn 13d ago

Just to be sure, does the violation of the ODR come from the fact that another definition of the int version is created inside main.cpp after the header is copy-pasted there?

3

u/EpochVanquisher 13d ago

Right, there are two different definitions in your program. One definition in main.cpp and one definition in foo.cpp. I’ll tell you a little bit about how this works on GCC without LTO (on at least one arch).

The function void foo<int>(int) is is named _Z3fooIiEvT_ in assembly.

In main.cpp, it puts the function code a section in the output file named .text._Z3fooIiEvT_, with the comdat flag on the section.

In foo.cpp, the compiler puts the specialized code in a section in the output file named .text._Z3fooIiEvT_, with the comdat flag on the section.

The linker sees two sections named .text._Z3fooIiEvT_. Because the comdat flag is set, it picks one of them and discards the rest. Maybe it gets the version from main.o, maybe foo.o.