Явное создание экземпляра шаблонного метода явно созданного шаблонного класса

#c #templates #explicit-instantiation

#c #шаблоны #явное создание экземпляра

Вопрос:

У меня есть класс A с шаблонным аргументом T, который ограничен двумя типами: T1 и T2. Из-за этого я явно создал экземпляр класса A для типов T1 и T2, так что функциональность A может быть определена в исходном файле и ее не нужно перекомпилировать каждый раз, когда включен A.hpp.

A. ГЭС:

 template<typename T>
class A {
public:
    void basicMethod();
};

template class A<T1>;
template class A<T2>;
 

A.cpp:

 template<typename T>
void A<T>::basicMethod() {
    // ...
}
 

Однако теперь я хочу добавить шаблонный метод как для <T1>, так и для <T2>, где аргумент шаблона снова ограничен двумя типами: S1 и S2:

A. ГЭС:

 template<typename T>
class A {
public:
    void basicMethod();
    template<typename S>
    void advancedMethod();
};

template class A<T1>;
template class A<T2>;

// How to explicitly instantiate A::advancedMethod here?
 

A.cpp:

 template<typename T>
void A<T>::basicMethod() {
    // ...
}

template<typename T>
template<typename S>
void A<T>::advancedMethod() {
    // ...
}
 

Как мне явно создать экземпляр <T>::advancedMethod<S> для (T, S) = {T1, T2} x {S1, S2}? Основываясь на том, что я нашел в Интернете, я попытался добавить подобные вещи в конец A.hpp:

 template void A<T1>::advancedMethod(S1);
template void A<T1>::advancedMethod(S2);
template void A<T2>::advancedMethod(S1);
template void A<T2>::advancedMethod(S2);
 

Однако это вообще не сработало.

Комментарии:

1. Я предполагаю, что второй метод шаблона в вашем .cpp должен быть advancedMethod .

2. @super Да, извините, я только что исправил это.

Ответ №1:

Явные определения создания экземпляра для шаблонов функций-членов шаблонов классов

Вы почти поняли это. Правильный синтаксис:

 template void A<T1>::advancedMethod<S1>();
template void A<T1>::advancedMethod<S2>();
template void A<T2>::advancedMethod<S1>();
template void A<T2>::advancedMethod<S2>();
 

В частности, следует отметить, что advancedMethod() это шаблон функции, параметризованный по параметру шаблона одного типа, без параметров функции: ваши явные определения создания экземпляра для разной специализации шаблона функции (для разных специализаций шаблона класса, в котором он определен) должны, как и явные определения создания экземпляра для шаблона класса, предоставлять шаблонаргументы ( <...> ) для указания специализаций, которые вы хотите явно создать.

Наконец, шаблон advancedMethod() функции, естественно, должен быть определен (либо через определение основного шаблона, либо в явной специализации) для каждой специализации, которая явно создается; в частности (cppreference):

Если шаблон функции, шаблон переменной, шаблон функции-члена или элемент функции-члена или элемент статических данных шаблона класса явно создается с явным определением экземпляра, определение шаблона должно присутствовать в той же единице преобразования.


Отделение определений шаблонных функций (функций-членов шаблона / class) от их объявлений: -timl.hpp шаблон

В результате приведенного выше требования при предоставлении явных определений экземпляра вы обычно размещаете их после определений связанных шаблонных объектов в исходном файле, где типичным вариантом использования является то, что пользователь общедоступного API шаблонного объекта не должен иметь доступа к определениям (как в вашемпример). Это важно, поскольку вы не можете явно создавать экземпляр одной и той же специализации дважды для разных единиц перевода, согласно [temp.spec]/5:

Для заданного набора аргументов шаблона,

  • явное определение экземпляра должно появляться в программе не более одного раза,
  • […]

Для диагностики нарушения этого правила реализация не требуется.

Таким образом, если вы поместите явные экземпляры в заголовок, а затем включите заголовок в более чем один исходный файл, ваша программа будет неправильно сформирована, NDR.

 // a.hpp
template<typename T>
class A {
public:
    void basicMethod();
    template<typename S>
    void advancedMethod();
};

// a-timpl.hpp
#include "a.hpp"

template<typename T>
void A<T>::basicMethod() {
    // ...
}

template<typename T>
template<typename S>
void A<T>::advancedMethod() {
    // ...
}

// a.cpp
#include "a-timpl.hpp"
#include "t1.hpp"
#include "t2.hpp"
#include "s1.hpp"
#include "s2.hpp"

// Explicit instantiation definitions for
// production intent.
template class A<T1>;
template class A<T2>;

template void A<T1>::advancedMethod<S1>();
template void A<T1>::advancedMethod<S2>();
template void A<T2>::advancedMethod<S1>();
template void A<T2>::advancedMethod<S2>();
 

Для теста вы аналогичным образом включаете файл -timpl.hpp заголовка, а не основной заголовок (основной заголовок предназначен для общедоступного API), так что вы можете использовать шаблонные определения для явного создания экземпляров специализаций, используемых в test:

 // a_test.cpp
#include "a-timpl.hpp"
#include "t1_mock.h"
#include "t2_mock.h"
#include "s1_mock.h"
#include "s2_mock.h"

// Explicit instantiation definitions for
// test intent.
template class A<T1Mock>;
template class A<T2Mock>;

template void A<T1Mock>::advancedMethod<S1Mock>();
template void A<T1Mock>::advancedMethod<S2Mock>();
template void A<T2Mock>::advancedMethod<S1Mock>();
template void A<T2Mock>::advancedMethod<S2Mock>();
 

Комментарии:

1. Спасибо за ваш ответ. Ваш синтаксис имел гораздо больше смысла, я только что видел, как люди в некоторых других местах помещали параметры шаблона в круглые скобки и не совсем понимали, почему. Я пробовал ваш синтаксис раньше, и он не работал, однако оказалось, что это потому, что я поместил явные экземпляры после определения класса / объявления метода в A.hpp вместо после определения метода. Теперь, похоже, все компилируется и связывается, хотя я еще не тестировал вызов экземпляров метода шаблона.

2. Что касается второй части вашего ответа: я не совсем понимаю, в каких конкретных ситуациях это вызывает проблемы? Прямо сейчас я помещаю экземпляры классов сразу после определения класса (так в заголовочном файле), а экземпляры методов сразу после их определений (так в исходном файле). Когда я перемещаю экземпляры класса в конец одного из исходных файлов этого класса, я получаю ошибки компоновщика.

3. @Wout12345 Согласно [temp.spec]/5 , для данного набора аргументов шаблона «явное определение экземпляра должно появляться не более одного раза в программе […] Для диагностики нарушения этого правила реализация не требуется.» . Если вы поместите явные экземпляры в заголовок, а затем включите заголовок более чем в один исходный файл, ваша программа будет неправильно сформирована, NDR (диагностика не требуется). …

4. @Wout12345 … Если вы получаете ошибки компоновщика, вы делаете что-то неправильно: возможно, вы включили a.hpp файл вместо a-timpl.hpp файла в исходный файл?

5. Рассматриваемый заголовок защищен от включения, поэтому он может появиться только один раз во время моего процесса компиляции, а заголовок является внутренним (не является частью общедоступного интерфейса), поэтому он не будет частью каких-либо других процессов компиляции. Означает ли это, что он никогда не дублируется?