为了减少模板密集型项目中的编译时间,我试图在一个单独的编译单元中显式实例化许多模板。因为这些模板依赖于enum class成员,所以我能够列出所有可能的实例化。我希望所有其他cpp文件只看到声明。虽然我能够做到这一点,但我遇到了试图分解显式实例化的问题。我将首先解释下面的两个工作示例,以解释我的问题到底是什么(示例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;如前所述,上面的编译和链接没有问题。然而,在实际的应用程序中,我有许多函数模板和更多的枚举成员。因此,我尝试通过将成员分组到一个新的类中来使我的生活变得更容易一些,这个类本身依赖于模板参数,并显式地为每个枚举值实例化这个类。
示例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>;这仍然有效(因为为了初始化指向成员的指针,必须实例化该成员),但是当枚举成员的数量很大时,仍然会很混乱。现在我终于可以谈这个问题了。我认为可以通过定义依赖于参数包的新类模板进一步分解,然后从包中的每种类型派生,如下所示:
示例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<...>,必须实例化每个派生类。至少我是这么想的。上面的内容会编译,但会导致链接器错误。在使用instantiate.o检查nm符号表时,确认没有实例化任何东西。为什么不行?
当然,我可以通过使用示例2,但它真的让我好奇,为什么事情会这样分解。
(与GCC 10.2.0合编)
编辑:在Clang8.0.1上也会发生同样的情况(尽管在分配函数指针:Function f1 = &Type::get<Value>;时必须显式地使用运算符的地址)
编辑:用户2b-t善意地通过_提供示例供人们试用。
发布于 2021-04-25 12:59:12
如果编译器看到该代码没有被引用,即使对于带有副作用的静态初始化,它也可以消除它,我认为这就是您的示例中的情况。它可以“证明”那些类实例化没有被使用,这样副作用就消失了。
对于一个非标准的解决方案,但是一个在g++上工作的解决方案(大概是clang,但没有测试)是用"used“属性标记您的静态数据成员:
template <Enum Value>
struct Instantiate
{
using Function = int (Type::*)() const;
static constexpr Function f1 __attribute__((used)) = &Type::get<Value>;
// many more member-functions
};更新
回顾一下标准,我的措辞似乎完全颠倒过来了:
“如果具有静态存储持续时间的对象具有初始化或具有副作用的析构函数,则即使该对象似乎未使用,也不应消除它,除非类对象或其副本可以按照.中的规定予以消除。”
所以我已经想了几十年了,现在我不知道我在想什么。)但考虑到该属性有帮助,这似乎是相关的。但现在我得知道到底发生了什么。
发布于 2021-04-25 12:37:48
我还不能给出一个很好的答案来解释为什么这不起作用(也许我以后可以这样做,或者其他人也可以),但是与Instantiate和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>;试试吧,这里。
https://stackoverflow.com/questions/67251672
复制相似问题