函数模板特化的显式实例化
Explicit instantiation of function template specialization
我正在尝试创建一个专门用于某些给定类型的全局函数模板。它看起来像这样:
A.h(主模板、模板特化、外部)
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(显式实例化)
template void foo<int>();
B.h
void bar();
B.cpp(包括A.h)
void bar() { foo<int>(); }
main.cpp
foo<int>();
bar();
编译器在我身上崩溃:“'void foo()' 的多个定义。我认为 extern 应该处理这个问题。B 编译单元不应该实例化 foo,而是使用 A 实例化link 时间,不是吗?我这里哪里错了?
请注意,如果我不专门化 foo,代码编译得很好。函数特化和实例化之间是否存在某种冲突?
这里不需要extern
来抑制实例化。通过声明显式特化,您已经告诉任何调用 foo<int>
的代码使用显式特化而不是主模板。相反,您只想在 A.h 中声明专业化,然后在 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; }
如果您想在某个翻译单元中提供主模板的显式实例化,并且不是显式特化,那么您使用extern
是合适的。
由于 Brian 的回答为您提供了一个可行的解决方案并解释了为什么您不需要 extern,因此我将详细说明您的情况。
首先,您说您的编译器崩溃了,所以您假设这是一个编译器错误。这不是实际情况。使用您的代码,A.cpp
、B.cpp
和 main.cpp
都可以自行成功编译。没有编译错误。在 visual studio 2017 CE 中,直到我尝试构建程序时,它才崩溃。这是我的 IDE 的构建错误。
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 ==========
你这里有一个 Linking
错误。由于多个定义的符号,它无法 link。
让我们检查一下您的代码:
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;
}
这里发生的是所有 3 个都在编译,但是当它转到 linker 通过传递三个目标文件来构建单个可执行文件时,它失败了。编译器只检查语言语法 - 语法并将其转换为目标文件。 linker 从编译器接收目标文件并为变量、函数、classes 等创建所有需要的符号。
它在 main 中查找并看到 #include "A.h"
和 #include "B.h"
所以预编译器已经进行了文本替换并在顶部粘贴了 A.h
和 B.h
页面,因此属于 A.cpp
和 B.cpp
翻译单元的 A.h
和 B.h
中的所有代码现在也位于 main.cpp
翻译单元中。所以它看到了 foo
模板对象,而且碰巧看到了不止一个定义!这与您使用 extern 无关。为了证明这一点,我可以删除您的一些代码,但仍然会生成相同的构建错误,因为它因多个定义而失败 link。
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
}
给出基本相同的构建-linking错误:
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 ==========
两者之间的唯一区别是在 A
和 B
中找到了多重定义,而在第二个实例中甚至没有使用 B
就找到了多重定义 A
。
要解决此问题,请使用 Brian's
答案!基本上,经验法则是在 headers
中包含 declarations
,在 cpp
文件中包含 definitions
,除非您的定义在特定的 class 或命名空间中,而不是在全局范围。
我正在尝试创建一个专门用于某些给定类型的全局函数模板。它看起来像这样:
A.h(主模板、模板特化、外部)
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(显式实例化)
template void foo<int>();
B.h
void bar();
B.cpp(包括A.h)
void bar() { foo<int>(); }
main.cpp
foo<int>();
bar();
编译器在我身上崩溃:“'void foo()' 的多个定义。我认为 extern 应该处理这个问题。B 编译单元不应该实例化 foo,而是使用 A 实例化link 时间,不是吗?我这里哪里错了?
请注意,如果我不专门化 foo,代码编译得很好。函数特化和实例化之间是否存在某种冲突?
这里不需要extern
来抑制实例化。通过声明显式特化,您已经告诉任何调用 foo<int>
的代码使用显式特化而不是主模板。相反,您只想在 A.h 中声明专业化,然后在 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; }
如果您想在某个翻译单元中提供主模板的显式实例化,并且不是显式特化,那么您使用extern
是合适的。
由于 Brian 的回答为您提供了一个可行的解决方案并解释了为什么您不需要 extern,因此我将详细说明您的情况。
首先,您说您的编译器崩溃了,所以您假设这是一个编译器错误。这不是实际情况。使用您的代码,A.cpp
、B.cpp
和 main.cpp
都可以自行成功编译。没有编译错误。在 visual studio 2017 CE 中,直到我尝试构建程序时,它才崩溃。这是我的 IDE 的构建错误。
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 ==========
你这里有一个 Linking
错误。由于多个定义的符号,它无法 link。
让我们检查一下您的代码:
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;
}
这里发生的是所有 3 个都在编译,但是当它转到 linker 通过传递三个目标文件来构建单个可执行文件时,它失败了。编译器只检查语言语法 - 语法并将其转换为目标文件。 linker 从编译器接收目标文件并为变量、函数、classes 等创建所有需要的符号。
它在 main 中查找并看到 #include "A.h"
和 #include "B.h"
所以预编译器已经进行了文本替换并在顶部粘贴了 A.h
和 B.h
页面,因此属于 A.cpp
和 B.cpp
翻译单元的 A.h
和 B.h
中的所有代码现在也位于 main.cpp
翻译单元中。所以它看到了 foo
模板对象,而且碰巧看到了不止一个定义!这与您使用 extern 无关。为了证明这一点,我可以删除您的一些代码,但仍然会生成相同的构建错误,因为它因多个定义而失败 link。
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
}
给出基本相同的构建-linking错误:
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 ==========
两者之间的唯一区别是在 A
和 B
中找到了多重定义,而在第二个实例中甚至没有使用 B
就找到了多重定义 A
。
要解决此问题,请使用 Brian's
答案!基本上,经验法则是在 headers
中包含 declarations
,在 cpp
文件中包含 definitions
,除非您的定义在特定的 class 或命名空间中,而不是在全局范围。