Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explicit instantiation of function template specialization

I am trying to create a global function template specialized for some given types. It looks something like that:

A.h (primary template, template specialization, extern)

template <typename T> void foo() { std::cout << "default stuff" << std::endl; }
template<> void foo<int>() { std::cout << "int stuff" << std::endl; }
extern template void foo<int>();

A.cpp (explicit instantiation)

template void foo<int>();

B.h

void bar();

B.cpp (includes A.h)

 void bar() { foo<int>(); }

main.cpp

foo<int>();
bar();

The compiler crashes on me: "multiple definitions of 'void foo()'. I thought that the extern was supposed to take care of this. The B compile unit should not instantiate foo, and instead use the A instantiation at link time, no? What am I getting wrong here?

Note that if I do not specialize foo, the code compiles just fine. Is there some kind of conflict between function specialization and instantiation?

like image 615
Touloudou Avatar asked Jan 27 '23 04:01

Touloudou


2 Answers

You don't need extern here to suppress instantiation. By declaring an explicit specialization, you're already telling any code that calls foo<int> to use the explicit specialization rather than the primary template. Instead, you simply want to declare the specialization in A.h and then define it in A.cpp:

// A.h
template <typename T> void foo() { std::cout << "default stuff" << std::endl; }
template <> void foo<int>();

// A.cpp
template <> void foo<int>() { std::cout << "int stuff" << std::endl; }

Your use of extern would be appropriate if you wanted to provide an explicit instantiation of the primary template in some translation unit, and not an explicit specialization.

like image 144
Brian Bi Avatar answered Feb 16 '23 03:02

Brian Bi


As Brian's answer gives you a working solution and explains why you don't need extern, I will elaborate a little bit more on your situation.

First, you stated that your compiler was crashing on you so you were assuming it was a compiler error. This is not the actual case. With your code as it was, A.cpp, B.cpp & main.cpp all compiled successfully on their own. There were no compilation errors. In visual studio 2017 CE, it wasn't until I tried to build the program until it crashed. Here is my IDE's build error.

1>------ Build started: Project: Temp, Configuration: Debug Win32 ------
1>B.obj : error LNK2005: "void __cdecl foo<int>(void)" (??$foo@H@@YAXXZ) already defined in A.obj
1>main.obj : error LNK2005: "void __cdecl foo<int>(void)" (??$foo@H@@YAXXZ) already defined in A.obj
1>C:\Users\...\Temp.exe : fatal error LNK1169: one or more multiply defined symbols found
1>Done building project "Temp.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

What you have here is a Linking error. It is failing to link because of multiple defined symbols.

Let's go over your code:

A.h

#pragma once
#include <iostream>

// Not just a declaration of void foo<T>() but also a definition!
template<typename T> void foo() { std::cout << "default stuff\n"; }
// Not just a declaration of void foo<int>() but also a specialized definition!
template<> void foo<int>() { std::cout << "int stuff\n"; }
// external explicit instantiation 
extern template void foo<int>();

A.cpp

#include "A.h"
// explicit instantiation 
template void foo<int>();

B.h

#pragma once
void bar();

B.cpp

#include "B.h"
#include "A.h"

void bar() {
    // instantiation to call foo
    foo<int>();
}

main.cpp

#include "A.h"
#include "B.h"
int main() {
    // instantiation to call foo
    foo<int>();
    bar();

    return 0;
}

What is happening here is all 3 are compiling, but when it goes to the linker to build a single executable by passing the three object files it fails. The compiler just checks for language grammar - syntax and converts it to object files. The linker receives the object files from the compiler and creates all of the needed symbols for variables, functions, classes etc.

It looks in main and sees #include "A.h" and #include "B.h" So the pre compiler had already did the text substitution and pasted A.h and B.h at the top of the page, so all of the code that was in A.h & B.h that belongs to A.cpp and B.cpp translations units are also now in main.cpp translation unit. So it sees the foo template objects and it happens to see more than one definition! It has nothing to do with your use of extern. To demonstrate this, I can remove some of your code and still generate the same build error where it fails to link because of multiple definitions.

A.h

#pragma once
#include <iostream>

template<typename T> void foo() { std::cout << "default stuff\n"; }
template<> void foo<int>() { std::cout << "int stuff\n"; }

A.cpp

#include "A.h"

main.cpp

#include "A.h"

int main() {
    foo<int>();

    return 0
}

Give basically the same build-linking error:

1>------ Build started: Project: Temp, Configuration: Debug Win32 ------
1>main.cpp
1>main.obj : error LNK2005: "void __cdecl foo<int>(void)" (??$foo@H@@YAXXZ) already defined in A.obj
1>C:\Users\...\Temp.exe : fatal error LNK1169: one or more multiply defined symbols found
1>Done building project "Temp.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

The only difference between the two is that the multiple definition is found in A & B where in the 2nd instance without even using B there is a multiple definition found A.

To resolve this use Brian's answer! Basically the rule of thumb is to have declarations in headers and definitions in cpp files unless if your definitions are in a specific class or namespace and not at the global scope.

like image 24
Francis Cugler Avatar answered Feb 16 '23 04:02

Francis Cugler