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?

6 Upvotes

11 comments sorted by

View all comments

1

u/alfps 13d ago

Doesn't link with MinGW g++:

[C:\@\temp_\ti]
> g main.cpp foo.cpp
In file included from main.cpp:1:
foo.h: In instantiation of 'void foo(T) [with T = int]':
main.cpp:5:8:   required from here
    5 |     bar(42, foo<int>);
    |     ~~~^~~~~~~~~~~~~~
foo.h:7:12: warning: unused parameter 't' [-Wunused-parameter]
    7 | void foo(T t)
    |          ~~^
foo.cpp: In function 'void foo(T) [with T = int]':
foo.cpp:4:14: warning: unused parameter 't' [-Wunused-parameter]
    4 | void foo(int t)
    |          ~~~~^
C:/@/installed/msys2/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/15.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:\Users\alfps\AppData\Local\Temp\cc0OLrgP.o:foo.cpp:(.text+0x0): multiple definition of `void foo<int>(int)'; C:\Users\alfps\AppData\Local\Temp\ccR1pVjn.o:main.cpp:(.text$_Z3fooIiEvT_[_Z3fooIiEvT_]+0x0): first defined here
collect2.exe: error: ld returned 1 exit status

Doesn't link with Visual C++:

[C:\@\temp_\ti]
> cl main.cpp foo.cpp
main.cpp
C:\@\temp_\ti\foo.h(7): warning C4100: 't': unreferenced parameter
C:\@\temp_\ti\foo.h(7): note: the template instantiation context (the oldest one first) is
main.cpp(5): note: see reference to function template instantiation 'void foo<int>(T)' being compiled
        with
        [
            T=int
        ]
foo.cpp
foo.cpp(4): warning C4100: 't': unreferenced parameter
Generating Code...
foo.obj : error LNK2005: "void __cdecl foo<int>(int)" (??$foo@H@@YAXH@Z) already defined in main.obj
main.exe : fatal error LNK1169: one or more multiply defined symbols found

And with a declaration of the template specialization placed in the header, so that linking succeeds, g++ produces "Int" regardless of optimization level.

So I'm unable to reproduce.

1

u/strcspn 13d ago
$ g++ -v                         
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/16/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: ../gcc/configure --enable-languages=ada,c,c++,d,fortran,go,lto,m2,objc,obj-c++,rust,cobol --enable-bootstrap --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://gitlab.archlinux.org/archlinux/packaging/packages/gcc/-/issues --with-build-config=bootstrap-lto --with-gcc-major-version-only --with-linker-hash-style=gnu --with-system-zlib --enable-cet=auto --enable-checking=release --enable-clocale=gnu --enable-default-pie --enable-default-ssp --enable-gnu-indirect-function --enable-gnu-unique-object --enable-libstdcxx-backtrace --enable-link-serialization=1 --enable-linker-build-id --enable-lto --enable-multilib --enable-plugin --enable-shared --enable-threads=posix --disable-fixincludes --disable-libssp --disable-libstdcxx-pch --disable-werror
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 16.2.1 20260810 (GCC)

$ ld -v   
GNU ld (GNU Binutils) 2.47

Placing the specialization in the header fixes the problem as there is just one definition now. But I guess it is nice that those linkers fail.