显式实例化的类模板

时间:2016-08-13 06:35:06

标签: c++ templates c++11

我在头文件(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的类型,该程序也会编译。
  • cppreference.com声称“编译器会将移动构造函数声明为其类的非显式内联公共成员”。但是,手动定义的Obj(int)构造函数也是内联的,但它已正确实例化。

(我在一个用GCC编译好的项目中收到了Clang这个错误,所以我认为这是一个Clang错误。但是,当我把问题简化为这个简单的情况时,GCC 5.4.0和Clang 3.8.0产生相同的结果。)

3 个答案:

答案 0 :(得分:5)

有趣。我认为你的代码是正确的,因为:

您的默认移动构造函数隐含inline,因为:

[dcl.fct.def.default]/5

  

...用户提供的显式默认功能(即明确地)   在第一次声明后的默认值)定义在哪里   它被明确默认。

[class.mfct]/1

  

可以在其类中定义成员函数([dcl.fct.def])   定义,在这种情况下,它是内联成员函数   ([dcl.fct.spec])

因此根据[temp.explicit]/10(强调我的)免除显式模板实例化:

  

除了内联函数和变量,带有类型的声明   从他们的初始值或返回值([dcl.spec.auto])推导出来,   文字类型的const变量,引用类型的变量和   类模板特化,显式实例化声明   具有抑制隐式实例化的效果   他们所指的实体。 [注意:意图是内联   作为显式实例化声明主题的函数   odr-used([basic.def.odr])时仍将隐式实例化   所以身体可以考虑内联,但没有   内联函数的外联副本将在。中生成   翻译单位。 - 结束说明]

事实上,如果您尝试-O0以外的任何优化模式,问题就会消失。

-O0是一种特殊模式,其中内联函数未内联。但是没关系,编译器必须在这种情况下生成inline默认的移动构造函数,就像它与其他构造函数一样。

所以对我来说这看起来像编译错误。另请查看LLVM#22763GCC#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)

你可能遇到编译器错误。

请参阅Demo

我在CLang上遇到了类似的行为,但无法找到错误报告。