Чтобы сократить время компиляции в проекте с большим количеством шаблонов, я пытаюсь явно создавать экземпляры многих шаблонов в отдельной единице компиляции. Поскольку эти шаблоны зависят от 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;
Как упоминалось ранее, вышеперечисленное компилируется и линкуется без проблем. Однако в реальном приложении у меня есть много шаблонов функций и еще много элементов перечисления. Поэтому я попытался несколько облегчить себе жизнь, сгруппировав элементы в новый класс, который сам зависит от параметра шаблона, и явно создал экземпляр этого класса для каждого из значений перечисления.
Пример 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)
Изменить: то же самое происходит в Clang 8.0.1 (хотя я должен явно использовать адрес оператора при назначении указателей функций: Function f1 = &Type::get<Value>;
)
Изменить: пользователь 2b-t любезно предоставил доступ к примерам через https://www.onlinegdb.com/HyGr7w0fv_ для экспериментов с людьми.
struct Type { template <Enum Value> int get () const { return static_cast<int>(Value); } };
) методов непосредственно внутри test.h? - person max66   schedule 25.04.2021__attribute__((used))
вообще не входит в стандарт C ++ и является языковым расширением gcc и clang. Руководство gcc объясняет это: gcc.gnu .org/onlinedocs/gcc-10.3.0/gcc/, не используется. - person aschepler   schedule 25.04.2021