我在头文件(template<bool VAR> struct Obj
)中声明了obj.h
模板,并带有显式自动移动构造函数(= default
)。
// obj.h
#pragma once
#include <vector>
template<bool VAR>
struct Obj {
std::vector<int> member;
Obj(int m): member(m) { }
Obj(Obj&&) = default;
int member_fun() const;
};
extern template struct Obj<false>;
extern template struct Obj<true>;
模板的成员函数在另一个文件(obj.cpp
)中定义,并明确实例化模板:
// obj.cpp
#include "obj.h"
template<bool VAR>
int Obj<VAR>::member_fun() const {
return 42;
}
template struct Obj<false>;
template struct Obj<true>;
然后从主文件(main.cpp
)中使用此模板:
// main.cpp
#include <utility>
#include "obj.h"
int main() {
Obj<true> o1(20);
Obj<true> o2(std::move(o1));
return o2.member_fun();
}
然后编译.cpp
并使用以下Makefile
链接在一起:
#CXX=clang++
CXX=g++
CXXFLAGS=-Wall -Wextra -std=c++14
a.out: obj.o main.o
$(CXX) $(CXXFLAGS) $^ -o a.out
obj.o: obj.cpp obj.h
$(CXX) $(CXXFLAGS) -c $< -o $@
main.o: main.cpp obj.h
$(CXX) $(CXXFLAGS) -c $< -o $@
但是,我收到一个链接器错误:undefined reference to 'Obj<true>::Obj(Obj<true>&&)'
- 编译器显然没有实例化构造函数。
Obj<true>::member_fun()
删除了对移动构造函数的引用,则main.cpp
已定义并且程序确实成功链接。extern template
,则程序会编译。int
代替std::vector<int>
作为member
的类型,该程序也会编译。Obj(int)
构造函数也是内联的,但它已正确实例化。(我在一个用GCC编译好的项目中收到了Clang这个错误,所以我认为这是一个Clang错误。但是,当我把问题简化为这个简单的情况时,GCC 5.4.0和Clang 3.8.0产生相同的结果。)
答案 0 :(得分:5)
有趣。我认为你的代码是正确的,因为:
您的默认移动构造函数隐含inline
,因为:
...用户提供的显式默认功能(即明确地) 在第一次声明后的默认值)定义在哪里 它被明确默认。
可以在其类中定义成员函数([dcl.fct.def]) 定义,在这种情况下,它是内联成员函数 ([dcl.fct.spec])
因此根据[temp.explicit]/10(强调我的)免除显式模板实例化:
除了内联函数和变量,带有类型的声明 从他们的初始值或返回值([dcl.spec.auto])推导出来, 文字类型的const变量,引用类型的变量和 类模板特化,显式实例化声明 具有抑制隐式实例化的效果 他们所指的实体。 [注意:意图是内联 作为显式实例化声明主题的函数 odr-used([basic.def.odr])时仍将隐式实例化 所以身体可以考虑内联,但没有 内联函数的外联副本将在。中生成 翻译单位。 - 结束说明]
事实上,如果您尝试-O0
以外的任何优化模式,问题就会消失。
-O0
是一种特殊模式,其中内联函数未内联。但是没关系,编译器必须在这种情况下生成inline
默认的移动构造函数,就像它与其他构造函数一样。
所以对我来说这看起来像编译错误。另请查看LLVM#22763和GCC#60796。
我看到至少2种可能的解决方法:
解决方案1
现在不要使用extern template ...
(编译时间会受到影响,但没有什么大不了的。)
解决方案2
欺骗编译器在obj.cpp
template<> Obj<true>::Obj(Obj&&) noexcept = default;
此选项仅用于-O0
模式。在生产代码中,将使用内联版本。
答案 1 :(得分:2)
互联网上没有关于此主题的大量信息。虽然看一些消息来源,但我会得出以下结论:
extern template
应该阻止隐式实例化,但在所有示例中,explicit
实例化都没有此extern template
定义。
从我可以阅读的内容,特别是关于提案和GCC邮件列表(请参阅下面的参考资料),extern template
不会阻止implicit
实例化,尽管模板的所有实例化都是如此。这将包括您的explicit
实例化。
如果实体是两个显式实例化的主题 声明和显式实例化定义 在同一翻译单位,定义应遵循声明。 - GCC邮件列表上的John Spicer
由此我得出结论,你应该删除你想要显式实例化的翻译单元中的extern template
。
参考文献:
答案 2 :(得分:0)