自动化显式模板实例化
Automating explicit template instantiation
为了减少大量使用模板的项目的编译时间,我尝试在单独的编译单元中显式实例化许多模板。因为这些模板依赖于 enum class
成员,所以我能够列出所有可能的实例化。我希望所有其他 cpp 文件只看到声明。虽然我能够做到这一点,但我 运行 遇到了试图分解显式实例化的问题。我将首先解释下面的 2 个工作示例,以解释我的问题到底是什么(示例 3):
示例 1
/* test.h
Contains the function-template-declaration, not the implementation.
*/
enum class Enum
{
Member1,
Member2,
Member3
};
struct Type
{
template <Enum Value>
int get() const;
};
/* test.cpp
Only the declaration is visible -> needs to link against correct instantiation.
*/
#include "test.h"
int main() {
std::cout << Type{}.get<Enum::Member1>() << '\n';
}
/* test.tpp
.tpp extension indicates that it contains template implementations.
*/
#include "test.h"
template <Enum Value>
int Type::get() const
{
return static_cast<int>(Value); // silly implementation
}
/* instantiate.cpp
Explicitly instantiate for each of the enum members.
*/
#include "test.tpp"
template int Type::get<Enum::Member1>() const;
template int Type::get<Enum::Member2>() const;
template int Type::get<Enum::Member3>() const;
如前所述,以上编译和链接没有问题。但是,在实际应用程序中,我有很多函数模板和更多的枚举成员。因此,我尝试通过将成员组合在一个新的 class 中来让我的生活更轻松一些,它本身取决于模板参数并为每个枚举值显式实例化此 class。
示例 2
// instantiate.cpp
#include "test.tpp"
template <Enum Value>
struct Instantiate
{
using Function = int (Type::*)() const;
static constexpr Function f1 = Type::get<Value>;
// many more member-functions
};
template class Instantiate<Enum::Member1>;
template class Instantiate<Enum::Member2>;
template class Instantiate<Enum::Member3>;
这仍然有效(因为为了初始化一个指向成员的指针,这个成员必须被实例化),但是当enum-members的数量很大时,它仍然会很乱。现在我终于可以解决这个问题了。我想我可以通过定义一个新的 class-template 来进一步分解,该模板依赖于参数包,然后从包中的每个类型派生,如下所示:
示例 3
// instantiate.cpp
#include "test.tpp"
template <Enum Value>
struct Instantiate { /* same as before */ };
template <Enum ... Pack>
struct InstantiateAll:
Instantiate<Pack> ...
{};
template class InstantiateAll<Enum::Member1, Enum::Member2, Enum::Member3>;
这应该行得通,对吧?为了实例化 InstantiateAll<...>
,每个派生的 classes 都必须被实例化。至少,我是这么想的。上面的编译但导致链接器错误。在检查 instantiate.o
的符号 -table 和 nm
后,确认没有任何实例化。为什么不呢?
当然,我可以通过示例 2 得到,但它真的让我很好奇为什么事情会这样崩溃。
(使用 GCC 10.2.0 编译)
编辑:同样发生在 Clang 8.0.1 上(尽管我必须在分配函数指针时明确使用操作符地址:Function f1 = &Type::get<Value>;
)
编辑:用户 2b-t 好心地通过 https://www.onlinegdb.com/HyGr7w0fv_ 提供了示例供人们试验。
我还不能给你一个很好的答案来说明为什么这不起作用(也许我可以稍后再做或者其他人可以)但不是让 Instantiate
和 InstantiateAll
有 只有一个可变参数 InstantiateAll
如下 works
template <Enum ... Pack>
struct InstantiateAll {
using Function = int (Type::*)() const;
static constexpr std::array<Function,sizeof...(Pack)> f = {&Type::get<Pack> ...};
};
template class InstantiateAll<Enum::Member1, Enum::Member2, Enum::Member3>;
试试看 here.
如果编译器发现代码未被引用,即使对于具有副作用的静态初始化,它也可以消除它,我认为您的示例就是这种情况。它可以“证明”未使用那些 class 实例化,因此副作用会丢失。
对于非标准解决方案,但适用于 g++(可能是 clang,但未经测试)的解决方案是使用“已使用”属性标记您的静态数据成员:
template <Enum Value>
struct Instantiate
{
using Function = int (Type::*)() const;
static constexpr Function f1 __attribute__((used)) = &Type::get<Value>;
// many more member-functions
};
更新
查看标准,措辞似乎完全倒退了:
"If an object of static storage duration has initialization or a
destructor with side effects, it shall not be eliminated even if it
appears to be unused, except that a class object or its copy may be
eliminated as specified in ..."
几十年来我一直在想这个问题,现在我不确定自己在想什么。 :) 但它似乎相关,因为该属性有帮助。但现在我必须了解发生了什么事。
为了减少大量使用模板的项目的编译时间,我尝试在单独的编译单元中显式实例化许多模板。因为这些模板依赖于 enum class
成员,所以我能够列出所有可能的实例化。我希望所有其他 cpp 文件只看到声明。虽然我能够做到这一点,但我 运行 遇到了试图分解显式实例化的问题。我将首先解释下面的 2 个工作示例,以解释我的问题到底是什么(示例 3):
示例 1
/* test.h
Contains the function-template-declaration, not the implementation.
*/
enum class Enum
{
Member1,
Member2,
Member3
};
struct Type
{
template <Enum Value>
int get() const;
};
/* test.cpp
Only the declaration is visible -> needs to link against correct instantiation.
*/
#include "test.h"
int main() {
std::cout << Type{}.get<Enum::Member1>() << '\n';
}
/* test.tpp
.tpp extension indicates that it contains template implementations.
*/
#include "test.h"
template <Enum Value>
int Type::get() const
{
return static_cast<int>(Value); // silly implementation
}
/* instantiate.cpp
Explicitly instantiate for each of the enum members.
*/
#include "test.tpp"
template int Type::get<Enum::Member1>() const;
template int Type::get<Enum::Member2>() const;
template int Type::get<Enum::Member3>() const;
如前所述,以上编译和链接没有问题。但是,在实际应用程序中,我有很多函数模板和更多的枚举成员。因此,我尝试通过将成员组合在一个新的 class 中来让我的生活更轻松一些,它本身取决于模板参数并为每个枚举值显式实例化此 class。
示例 2
// instantiate.cpp
#include "test.tpp"
template <Enum Value>
struct Instantiate
{
using Function = int (Type::*)() const;
static constexpr Function f1 = Type::get<Value>;
// many more member-functions
};
template class Instantiate<Enum::Member1>;
template class Instantiate<Enum::Member2>;
template class Instantiate<Enum::Member3>;
这仍然有效(因为为了初始化一个指向成员的指针,这个成员必须被实例化),但是当enum-members的数量很大时,它仍然会很乱。现在我终于可以解决这个问题了。我想我可以通过定义一个新的 class-template 来进一步分解,该模板依赖于参数包,然后从包中的每个类型派生,如下所示:
示例 3
// instantiate.cpp
#include "test.tpp"
template <Enum Value>
struct Instantiate { /* same as before */ };
template <Enum ... Pack>
struct InstantiateAll:
Instantiate<Pack> ...
{};
template class InstantiateAll<Enum::Member1, Enum::Member2, Enum::Member3>;
这应该行得通,对吧?为了实例化 InstantiateAll<...>
,每个派生的 classes 都必须被实例化。至少,我是这么想的。上面的编译但导致链接器错误。在检查 instantiate.o
的符号 -table 和 nm
后,确认没有任何实例化。为什么不呢?
当然,我可以通过示例 2 得到,但它真的让我很好奇为什么事情会这样崩溃。
(使用 GCC 10.2.0 编译)
编辑:同样发生在 Clang 8.0.1 上(尽管我必须在分配函数指针时明确使用操作符地址:Function f1 = &Type::get<Value>;
)
编辑:用户 2b-t 好心地通过 https://www.onlinegdb.com/HyGr7w0fv_ 提供了示例供人们试验。
我还不能给你一个很好的答案来说明为什么这不起作用(也许我可以稍后再做或者其他人可以)但不是让 Instantiate
和 InstantiateAll
有 只有一个可变参数 InstantiateAll
如下 works
template <Enum ... Pack>
struct InstantiateAll {
using Function = int (Type::*)() const;
static constexpr std::array<Function,sizeof...(Pack)> f = {&Type::get<Pack> ...};
};
template class InstantiateAll<Enum::Member1, Enum::Member2, Enum::Member3>;
试试看 here.
如果编译器发现代码未被引用,即使对于具有副作用的静态初始化,它也可以消除它,我认为您的示例就是这种情况。它可以“证明”未使用那些 class 实例化,因此副作用会丢失。
对于非标准解决方案,但适用于 g++(可能是 clang,但未经测试)的解决方案是使用“已使用”属性标记您的静态数据成员:
template <Enum Value>
struct Instantiate
{
using Function = int (Type::*)() const;
static constexpr Function f1 __attribute__((used)) = &Type::get<Value>;
// many more member-functions
};
更新
查看标准,措辞似乎完全倒退了:
"If an object of static storage duration has initialization or a destructor with side effects, it shall not be eliminated even if it appears to be unused, except that a class object or its copy may be eliminated as specified in ..."
几十年来我一直在想这个问题,现在我不确定自己在想什么。 :) 但它似乎相关,因为该属性有帮助。但现在我必须了解发生了什么事。