将“外部模板”与 third-party header-only 库一起使用
Using `extern template` with third-party header-only library
我正在使用 glm
library, which is a header-only collection of math utilities intended for 3D graphics. By using -ftime-trace
on Clang and ClangBuildAnalyzer
,我注意到实例化 glm
类型花费了大量时间:
**** Templates that took longest to instantiate:
16872 ms: glm::vec<4, signed char, glm::packed_highp> (78 times, avg 216 ms)
15675 ms: glm::vec<4, unsigned char, glm::packed_highp> (78 times, avg 200 ms)
15578 ms: glm::vec<4, float, glm::packed_highp> (78 times, avg 199 ms)
...
所以,我决定为 glm
创建一个包装器 header/source 对,并使用 extern template
来避免不必要的实例化:
// glmwrapper.h
#pragma once
#include <glm.hpp>
extern template struct glm::vec<4, signed char, glm::packed_highp>;
extern template struct glm::vec<4, unsigned char, glm::packed_highp>;
extern template struct glm::vec<4, float, glm::packed_highp>;
// glmwrapper.cpp
template struct glm::vec<4, signed char, glm::packed_highp>;
template struct glm::vec<4, unsigned char, glm::packed_highp>;
template struct glm::vec<4, float, glm::packed_highp>;
现在,在我的项目中,我不再包含 <glm.hpp>
,而是包含 "glmwrapper.h"
。不幸的是,这并没有改变任何东西。再次使用 -ftime-trace
和 ClangBuildAnalyzer
会报告相同数量的实例化。也没有可测量的编译时间差异。
我 怀疑 这是因为 #include <glm.hpp>
实际上最终包含了模板定义,此时后续的 extern template
声明只是多余的.
有没有办法不用修改glm
库就可以达到我想要的效果?
在伪代码中,我有点想要这样的东西:
// glmwrapper.h (psuedocode)
#pragma once
#include <glm.hpp>
// Make definition of the templates unavailable:
undefine template struct glm::vec<4, signed char, glm::packed_highp>;
undefine template struct glm::vec<4, unsigned char, glm::packed_highp>;
undefine template struct glm::vec<4, float, glm::packed_highp>;
// Make declaration of the templates available:
extern template struct glm::vec<4, signed char, glm::packed_highp>;
extern template struct glm::vec<4, unsigned char, glm::packed_highp>;
extern template struct glm::vec<4, float, glm::packed_highp>;
// glmwrapper.cpp (psuedocode)
// Define templates only in the `.cpp`, not in the header:
template struct glm::vec<4, signed char, glm::packed_highp>;
template struct glm::vec<4, unsigned char, glm::packed_highp>;
template struct glm::vec<4, float, glm::packed_highp>;
不幸的是,没有办法避免这些实例化。 class 模板的显式实例化声明不会 阻止 该模板的(隐式)实例化;它只是阻止实例化其非内联、非模板成员函数(通常是 none 个成员函数!),因为其他一些翻译单元将提供实际的函数符号和目标代码。
不是看到模板定义导致实例化(哪个特化会被实例化?)。原因是要求 class 完整的代码仍然需要知道它的 layout 和成员函数声明(用于重载决议),并且通常没有办法知道那些没有实例化 class:
template<class T> struct A : T::B {
typename std::conditional<sizeof(T)<8,long,short>::type first;
typename T::X second;
A() noexcept(T::y)=default; // perhaps deleted
using T::B::foo;
void foo(T);
// and so on…
};
void f() {A<C> a; a.foo(a.first);} // …maybe?
这种“透明度”也扩展到模板化实体的several other kinds:如果编译需要模板的定义,则为[=21=生成的符号]linker 无关紧要。
好消息是 C++20 的 modules 应该有助于解决这样的情况:模块接口中的显式实例化 definition将导致典型的实现将实例化的 class 定义与其余模块接口数据一起缓存,避免在导入翻译单元时同时进行解析和实例化。模块还删除了 class 中定义的 class 成员和朋友的隐式 inline
(这在很长一段时间内都没有多大意义),增加了数量(或者,换句话说,显式实例化声明阻止隐式实例化的函数的便利性。
我正在使用 glm
library, which is a header-only collection of math utilities intended for 3D graphics. By using -ftime-trace
on Clang and ClangBuildAnalyzer
,我注意到实例化 glm
类型花费了大量时间:
**** Templates that took longest to instantiate:
16872 ms: glm::vec<4, signed char, glm::packed_highp> (78 times, avg 216 ms)
15675 ms: glm::vec<4, unsigned char, glm::packed_highp> (78 times, avg 200 ms)
15578 ms: glm::vec<4, float, glm::packed_highp> (78 times, avg 199 ms)
...
所以,我决定为 glm
创建一个包装器 header/source 对,并使用 extern template
来避免不必要的实例化:
// glmwrapper.h
#pragma once
#include <glm.hpp>
extern template struct glm::vec<4, signed char, glm::packed_highp>;
extern template struct glm::vec<4, unsigned char, glm::packed_highp>;
extern template struct glm::vec<4, float, glm::packed_highp>;
// glmwrapper.cpp
template struct glm::vec<4, signed char, glm::packed_highp>;
template struct glm::vec<4, unsigned char, glm::packed_highp>;
template struct glm::vec<4, float, glm::packed_highp>;
现在,在我的项目中,我不再包含 <glm.hpp>
,而是包含 "glmwrapper.h"
。不幸的是,这并没有改变任何东西。再次使用 -ftime-trace
和 ClangBuildAnalyzer
会报告相同数量的实例化。也没有可测量的编译时间差异。
我 怀疑 这是因为 #include <glm.hpp>
实际上最终包含了模板定义,此时后续的 extern template
声明只是多余的.
有没有办法不用修改glm
库就可以达到我想要的效果?
在伪代码中,我有点想要这样的东西:
// glmwrapper.h (psuedocode)
#pragma once
#include <glm.hpp>
// Make definition of the templates unavailable:
undefine template struct glm::vec<4, signed char, glm::packed_highp>;
undefine template struct glm::vec<4, unsigned char, glm::packed_highp>;
undefine template struct glm::vec<4, float, glm::packed_highp>;
// Make declaration of the templates available:
extern template struct glm::vec<4, signed char, glm::packed_highp>;
extern template struct glm::vec<4, unsigned char, glm::packed_highp>;
extern template struct glm::vec<4, float, glm::packed_highp>;
// glmwrapper.cpp (psuedocode)
// Define templates only in the `.cpp`, not in the header:
template struct glm::vec<4, signed char, glm::packed_highp>;
template struct glm::vec<4, unsigned char, glm::packed_highp>;
template struct glm::vec<4, float, glm::packed_highp>;
不幸的是,没有办法避免这些实例化。 class 模板的显式实例化声明不会 阻止 该模板的(隐式)实例化;它只是阻止实例化其非内联、非模板成员函数(通常是 none 个成员函数!),因为其他一些翻译单元将提供实际的函数符号和目标代码。
不是看到模板定义导致实例化(哪个特化会被实例化?)。原因是要求 class 完整的代码仍然需要知道它的 layout 和成员函数声明(用于重载决议),并且通常没有办法知道那些没有实例化 class:
template<class T> struct A : T::B {
typename std::conditional<sizeof(T)<8,long,short>::type first;
typename T::X second;
A() noexcept(T::y)=default; // perhaps deleted
using T::B::foo;
void foo(T);
// and so on…
};
void f() {A<C> a; a.foo(a.first);} // …maybe?
这种“透明度”也扩展到模板化实体的several other kinds:如果编译需要模板的定义,则为[=21=生成的符号]linker 无关紧要。
好消息是 C++20 的 modules 应该有助于解决这样的情况:模块接口中的显式实例化 definition将导致典型的实现将实例化的 class 定义与其余模块接口数据一起缓存,避免在导入翻译单元时同时进行解析和实例化。模块还删除了 class 中定义的 class 成员和朋友的隐式 inline
(这在很长一段时间内都没有多大意义),增加了数量(或者,换句话说,显式实例化声明阻止隐式实例化的函数的便利性。