Как мне получить специализацию функции шаблона в исходном файле? - PullRequest
1 голос
/ 20 марта 2020

Эта проблема возникла у нас при разработке нашего двигателя. Мы хотим иметь

template <typename T>
std::pair<const uint8_t*, size_t> get_resource()
{
    return {nullptr, 0ull};
}

и специализироваться на многих T с. Важно то, что мы хотим, чтобы специализации находились в отдельных исходных (.cpp) файлах и компилировались в двоичный файл библиотеки нашего движка stati c. Размещение специализаций в исходных файлах жизненно важно, поскольку вполне возможно, что эти функции будут часто меняться и #include d другими заголовками огромной кодовой базы (не менее нескольких сотен тысяч Lo C) движка , Таким образом, если они находятся в заголовочных файлах, это может привести к перестройке большой части проекта после изменения одного из определений специализаций. Я не смог найти помощь для такой проблемы, поэтому здесь я публикую наше решение.

1 Ответ

2 голосов
/ 20 марта 2020

Прежде всего: нет проблем с размещением специализации шаблона функции в исходном файле, поскольку - когда она специализирована - это функция, как и любая другая. Хитрость заключается в том, что источники #include получают заголовок с определением шаблона, что такая специализация существует. Очевидно, в c ++ нет способа объявить специализацию . То, что вы можете сделать, это принудительно создать экземпляр шаблона get_resource для некоторого T (что нам, очевидно, нужно сделать в любом случае, потому что мы хотим, чтобы специализации были скомпилированы в двоичный файл библиотеки). Это можно сделать с помощью следующего синтаксиса:

//force instantiation of get_resource for T=SOME_TYPE
template std::pair<const uint8_t*, size_t> get_resource<SOME_TYPE>();

Но: если мы поместим его в заголовочный файл, сразу после определения шаблона, мы захотим иметь возможность специализировать его, потому что вы не можете специализировать то, что уже создано , Если мы поместим его в исходный файл, сразу после специализации, источники #include с заголовком с определением шаблона не смогут его увидеть, потому что он никогда не объявлялся существующим. Это тот момент, когда extern template приходит на помощь. Принудительное создание экземпляра с дополнительным ключевым словом extern является своего рода объявлением, что эта функция шаблона будет создана для некоторого заданного T (SOME_TYPE в примере выше), но пока нет. Итак, теперь мы можем использовать это ключевое слово extern для объявления принудительного создания экземпляра в заголовочном файле, затем в исходном файле определить нашу специализацию и после , которые будут выполнять фактическое принудительное создание (без extern). Это обеспечит видимость специализации для источников #include, и в то же время ее определение может быть помещено в исходный файл (скомпилирован как отдельная единица перевода).

TL; DR

Заголовочный файл

template <typename T>
std::pair<const uint8_t*, size_t> get_resource()
{
    return {nullptr, 0ull};
}

extern template std::pair<const uint8_t*, size_t> get_resource<SOME_TYPE>();

Исходный файл

template<> std::pair<const uint8_t*, size_t> get_resource<SOME_TYPE>()
{
    return {reinterpret_cast<const uint8_t*>("whatever"), 9ull};
}

template std::pair<const uint8_t*, size_t> get_resource<SOME_TYPE>();
...