Explicit instantiation of template template method
От: ezdoctor  
Дата: 02.03.18 17:57
Оценка:
Не удается явно инстанциировать шаблонный метод шаблонного класса. Буду благодарен, если подскажете, что не так.

Имеем заголовочный файл с определением шаблонного класса, где методы всего лишь объявлены, но не определены:
//header.h
template<typename T>
class C {
public:
    void memberFunction();

    template <typename S>
    void templateMemberFunction();
};


Как видим, объявлены два метода, один обычный нестатический метод, другой метод шаблонный.
Далее, есть файл, где данный класс и оба его метода используются. Иными словами, класс инстанциируется типом T=int, а его шаблонный метод вдобавок типом S=double:
//main.cpp
#include<header.h>

...
    C<int> c;
    c.memberFunction();
    c.templateMemberFunction<double>();


Определение самих методов "спрятано" в другом файле:
//class_implementation.cpp
#include<header.h>

template<>
void C<int>::memberFunction() {
}

template <>
template <>
void C<int>::templateMemberFunction<double>() {
}

На самом деле это даже не определение, а специализация шаблонных методов определенными типами. Если построить проект на этом этапе, то оба метода не будут найдены линкером, потому что информация о том, какими именно типами инстанциируются класс и метод в файле main.cpp, отсутствует в процессе компиляции class_implementation.cpp. Для решения этой проблемы требуется инстанциировать класс нужными нам типами в файле class_implementation.cpp. Например, так:
//class_implementation.cpp
void this_function_is_not_used_anywhere() {
    C<int> c;
    c.memberFunction();
    c.templateMemberFunction<double>();
}

То есть определяем функцию, которая нигде не используется (и потому линкером будет выброшена), которая использует нужный нам класс и его методы, инстанциируя нужными типами. Как следствие, в объектном файле class_implementation.o содержатся определения всех нужных методов, и линкер находит все необходимое для сборки проекта. Проблема в том, что инстанциирование методом this_function_is_not_used_anywhere есть кривизна, которой хочется избежать. И этого удается избежать для memberFunction явной инстанциацией:
//class_implementation.cpp
template void C<int>::memberFunction();

Или даже лучше так (инстанциируем не метод, а весь класс):
//class_implementation.cpp
template class C<int>;

Но вот для шаблонного метода templateMemberFunction сделать по аналогии у меня не выходит. Следующий код компилируется, но линкер все равно не находит нужного определения метода:
//class_implementation.cpp
template void C<int>::templateMemberFunction<double>();


Данный код написан на коленке, выброшено все, лишь бы проиллюстрировать идею, так что не обращайте внимание на возможные синтактические ошибки. Важно следующее: не удается сделать явную инстанциацию шаблонного метода шаблонного класса, при том, что просто метод этого шаблонного класса инстанциируется без проблем. Компилятор VS2013. Есть ли идеи по поводу того, что здесь не так?
explicit instantiation
Re: Explicit instantiation of template template method
От: rg45 СССР  
Дата: 02.03.18 18:17
Оценка:
Здравствуйте, ezdoctor, Вы писали:

E>Или даже лучше так (инстанциируем не метод, а весь класс):

E>
E>//class_implementation.cpp
E>template class C<int>;
E>

E>Но вот для шаблонного метода templateMemberFunction сделать по аналогии у меня не выходит. Следующий код компилируется, но линкер все равно не находит нужного определения метода:
E>
E>//class_implementation.cpp
E>template void C<int>::templateMemberFunction<double>();
E>


Ну такая задача обычно через явное инстанцирование и решается. По идее, все, что нужно тебе сделать — это инстанцировать весь класс, а потом еще отдельно шаблонную функцию:

template class C<int>;
template void C<int>::templateMemberFunction<double>();


Ты делаешь и то, и другое и все равно не находится?
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: Explicit instantiation of template template method
От: rg45 СССР  
Дата: 02.03.18 18:34
Оценка:
Здравствуйте, ezdoctor, Вы писали:

E>Или даже лучше так (инстанциируем не метод, а весь класс):

E>
E>//class_implementation.cpp
E>template class C<int>;
E>

E>Но вот для шаблонного метода templateMemberFunction сделать по аналогии у меня не выходит. Следующий код компилируется, но линкер все равно не находит нужного определения метода:
E>
E>//class_implementation.cpp
E>template void C<int>::templateMemberFunction<double>();
E>


Я попробовал воспроизвести это в 2015 студии — все работает. Возможно, есть в твоем случае какие-то особенности? Может, какие-то специализации? Учитываешь, что в точке явного инстанцирования определения всех методов должны быть видны? В общем, чего-то ты недоговариваешь. Подробности — в студию!
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 02.03.2018 18:35 rg45 . Предыдущая версия . Еще …
Отредактировано 02.03.2018 18:34 rg45 . Предыдущая версия .
Отредактировано 02.03.2018 18:34 rg45 . Предыдущая версия .
Re: Explicit instantiation of template template method
От: Constructor  
Дата: 02.03.18 18:59
Оценка:
Здравствуйте, ezdoctor, Вы писали:

E>Определение самих методов "спрятано" в другом файле:

E>
E>//class_implementation.cpp
E>#include<header.h>

E>template<>
E>void C<int>::memberFunction() {
E>}

E>template <>
E>template <>
E>void C<int>::templateMemberFunction<double>() {
E>}
E>

E>На самом деле это даже не определение, а специализация шаблонных методов определенными типами. Если построить проект на этом этапе, то оба метода не будут найдены линкером, потому что информация о том, какими именно типами инстанциируются класс и метод в файле main.cpp, отсутствует в процессе компиляции class_implementation.cpp.

Явные специализации методов должны быть объявлены перед использованием. Т.е. на данном этапе достаточно поместить объявления явных специализаций в заголовочный файл:
template<>
void C<int>::memberFunction();

template <>
template <>
void C<int>::templateMemberFunction<double>();

...и линковка должна пройти успешно.
Вообще, явные специализации шаблонов функций/методов нужно рассматривать как обычные функции со специфической декорацией. Со всеми вытекающими:

E>Но вот для шаблонного метода templateMemberFunction сделать по аналогии у меня не выходит. Следующий код компилируется, но линкер все равно не находит нужного определения метода:

E>
E>//class_implementation.cpp
E>template void C<int>::templateMemberFunction<double>();
E>


(В свете вышесказанного следующая информация не слишком актуальна, но все же.)
Попробуйте добавить объявление явного инстанцирования в заголовочный файл (эта возможность появилась в С++11):
extern template void C<int>::templateMemberFunction<double>();
Re[2]: Explicit instantiation of template template method
От: rean  
Дата: 02.03.18 19:09
Оценка:
deleted
Отредактировано 22.04.2019 9:17 deleted2 . Предыдущая версия .
Re[3]: Explicit instantiation of template template method
От: Constructor  
Дата: 02.03.18 19:25
Оценка:
Здравствуйте, rean, Вы писали:

C>>Явные специализации методов должны быть объявлены перед использованием.


R>Они же и так уже объявлены в классе. Дополнительное объявление не обязательно.


По стандарту обязательно (см. главу Explicit specialization [temp.expl.spec], абзацы 6 и 7 в актуальной версии стандарта; выделение жирным мое):

6 If a template, a member template or a member of a class template is explicitly specialized then that
specialization shall be declared before the first use of that specialization that would cause an implicit
instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required
. If
the program does not provide a definition for an explicit specialization and either the specialization is used in
a way that would cause an implicit instantiation to take place or the member is a virtual member function,
the program is ill-formed, no diagnostic required. An implicit instantiation is never generated for an explicit
specialization that is declared but not defined. [ Example:

class String { };
template<class T> class Array { /* ... */ };
template<class T> void sort(Array<T>& v) { /* ... */ }
void f(Array<String>& v) {
  sort(v); // use primary template sort(Array<T>&), T is String
}
template<> void sort<String>(Array<String>& v); // error: specialization after use of primary template
template<> void sort<>(Array<char*>& v); // OK: sort<char*> not yet used
template<class T> struct A {
  enum E : T;
  enum class S : T;
};
template<> enum A<int>::E : int { eint }; // OK
template<> enum class A<int>::S : int { sint }; // OK
template<class T> enum A<T>::E : T { eT };
template<class T> enum class A<T>::S : T { sT };
template<> enum A<char>::E : char { echar }; // ill-formed, A<char>::E was instantiated
// when A<char> was instantiated
template<> enum class A<char>::S : char { schar }; // OK

end example ]
7 The placement of explicit specialization declarations for function templates, class templates, variable templates,
member functions of class templates, static data members of class templates, member classes of class templates,
member enumerations of class templates, member class templates of class templates, member function
templates of class templates, static data member templates of class templates, member functions of member
templates of class templates, member functions of member templates of non-template classes, static data
member templates of non-template classes, member function templates of member classes of class templates,
etc., and the placement of partial specialization declarations of class templates, variable templates, member
class templates of non-template classes, static data member templates of non-template classes, member class
templates of class templates, etc., can affect whether a program is well-formed according to the relative
positioning of the explicit specialization declarations and their points of instantiation in the translation unit
as specified above and below. When writing a specialization, be careful about its location; or to make it
compile will be such a trial as to kindle its self-immolation
.

Отредактировано 02.03.2018 19:27 Constructor . Предыдущая версия .
Re[4]: Explicit instantiation of template template method
От: rg45 СССР  
Дата: 02.03.18 19:42
Оценка: +1
Здравствуйте, Constructor, Вы писали:

C>По стандарту обязательно (см. главу Explicit specialization [temp.expl.spec], абзацы 6 и 7 в актуальной версии стандарта; выделение жирным мое):


C>[q]6 If a template, a member template or a member of a class template is explicitly specialized then that

C>specialization shall be declared before the first use of that specialization that would cause an implicit
C>instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required
. If
C>the program does not provide a definition for an explicit specialization and either the specialization is used in
C>a way that would cause an implicit instantiation to take place or the member is a virtual member function,
C>the program is ill-formed, no diagnostic required. An implicit instantiation is never generated for an explicit
C>specialization that is declared but not defined. [ Example:
C>
C>class String { };
C>template<class T> class Array { /* ... */ };
C>template<class T> void sort(Array<T>& v) { /* ... */ }
C>void f(Array<String>& v) {
C>  sort(v); // use primary template sort(Array<T>&), T is String
C>}
C>template<> void sort<String>(Array<String>& v); // error: specialization after use of primary template
C>


Это актуально только для случаев неявного инстанцирования. Для того, чтобы неявное инстанцирование прошло правильно, в точке первого использования должны быть видны и главный шаблон, и все специализации. В рассматриваемом же случае никаких неявных инстанцирований нет, поэтому ошибки подобного рода исключены. В точках использования компилятору достаточно знать только имя, а линкер это имя потом найдет. И отдельного объявления всех специализаций для этого не требуются.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: Explicit instantiation of template template method
От: ezdoctor  
Дата: 02.03.18 19:45
Оценка:
Здравствуйте, rg45, Вы писали:

R>Я попробовал воспроизвести это в 2015 студии — все работает. Возможно, есть в твоем случае какие-то особенности? Может, какие-то специализации? Учитываешь, что в точке явного инстанцирования определения всех методов должны быть видны? В общем, чего-то ты недоговариваешь. Подробности — в студию!

Подробности, конечно, присутствуют. Там и не void возвращается, а другой темплейтный класс. И не всегда возвращается, т.к. метод существует не всегда, а только когда условие в enable_if выполняется, а выполняется это не для каждого типа. И типы далеко не int и double . Но эти подробности я убрал, причем не только в данном искусственном примере, но и в своем коде ради эксперимента.
Наверное, наиболее интересная подробность -- это то, что я все это наблюдаю на VS2013. С учетом того, что уже два человека подтвердили, что на VS2015 это не проявляется, попробую проверить это сам. Если гипотеза подтвердится, то это многое объяснит, если нет -- отпишусь с указанием иных подробностей.

За помощь спасибо.
Re[5]: Explicit instantiation of template template method
От: ezdoctor  
Дата: 02.03.18 19:50
Оценка:
Здравствуйте, rg45, Вы писали:

R>Здравствуйте, Constructor, Вы писали:


C>>По стандарту обязательно (см. главу Explicit specialization [temp.expl.spec], абзацы 6 и 7 в актуальной версии стандарта; выделение жирным мое):


C>>[q]6 If a template, a member template or a member of a class template is explicitly specialized then that

C>>specialization shall be declared before the first use of that specialization that would cause an implicit
C>>instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required
. If
C>>the program does not provide a definition for an explicit specialization and either the specialization is used in
C>>a way that would cause an implicit instantiation to take place or the member is a virtual member function,
C>>the program is ill-formed, no diagnostic required. An implicit instantiation is never generated for an explicit
C>>specialization that is declared but not defined. [ Example:
C>>
C>>class String { };
C>>template<class T> class Array { /* ... */ };
C>>template<class T> void sort(Array<T>& v) { /* ... */ }
C>>void f(Array<String>& v) {
C>>  sort(v); // use primary template sort(Array<T>&), T is String
C>>}
C>>template<> void sort<String>(Array<String>& v); // error: specialization after use of primary template
C>>


R>Это актуально только для случаев неявного инстанцирования. Для того, чтобы неявное инстанцирование прошло правильно, в точке первого использования должны быть видны и главный шаблон, и все специализации. В рассматриваемом же случае никаких неявных инстанцирований нет, поэтому ошибки подобного рода исключены. В точках использования компилятору достаточно знать только имя, а линкер это имя потом найдет. И отдельного объявления всех специализаций для этого не требуются.


Именно. Это как раз мое желаемое поведение: в файле, где шаблонные классы фактически используются, нет никакой информации о том, как они реализованы. А реализованы они сильно по-разному в зависимости от параметров шаблонов. Поэтому есть те модули, где видны только определения шаблонного класса, а есть те модули, где специализируются конкретные реализации. Осталось только слинковать, а для этого надо инстанциировать в том месте, где видны все специализации.
К моему изначальному вопросу: пока наиболее правдоподобной версией является то, что это специфика VS2013. Да, на компилятор все валить не комильфо, так что буду проверять гипотезу.
Re[3]: Explicit instantiation of template template method
От: ezdoctor  
Дата: 02.03.18 21:30
Оценка:
Здравствуйте, ezdoctor, Вы писали:

E>Здравствуйте, rg45, Вы писали:


R>>Я попробовал воспроизвести это в 2015 студии — все работает. Возможно, есть в твоем случае какие-то особенности? Может, какие-то специализации? Учитываешь, что в точке явного инстанцирования определения всех методов должны быть видны? В общем, чего-то ты недоговариваешь. Подробности — в студию!

E>Подробности, конечно, присутствуют. Там и не void возвращается, а другой темплейтный класс. И не всегда возвращается, т.к. метод существует не всегда, а только когда условие в enable_if выполняется, а выполняется это не для каждого типа. И типы далеко не int и double . Но эти подробности я убрал, причем не только в данном искусственном примере, но и в своем коде ради эксперимента.
E>Наверное, наиболее интересная подробность -- это то, что я все это наблюдаю на VS2013. С учетом того, что уже два человека подтвердили, что на VS2015 это не проявляется, попробую проверить это сам. Если гипотеза подтвердится, то это многое объяснит, если нет -- отпишусь с указанием иных подробностей.

На самом деле мой же минималистичный пример с успехом заработал на VS2013 Более того, даже явной инстанциации не потребовалось, что логично, т.к. зачем инстанциировать полностью специализированный метод. Значит, начинаю искать подробности... И они нашлись. Все то же самое, но добавим перчику в виде inline:

//header.h
template<typename T>
class C {
public:
    inline void memberFunction();

    template <typename S>
    inline void templateMemberFunction();
};


Целью данного инлайна (ныне рудиментарного) было проинструктировать линковщик об отсутствии необходимости ругаться на определение этих методов в двух модулях сразу: раньше эти методы определялись в заголовочном файле, так что (одинаковый) код попадал в два объектника сразу. Ровно то же самое происходит в случае, если метод определяется прямо внутри определения класса, компилятор такие методы помечает как inline, линковщик не ругается, и все хорошо. Ныне определения методов из заголовков перекочевали в cpp, и необходимость в inline отпала.

Но вот в определении часть методов сохранила спецификатор inline, часть его потеряла, но это уже волюнтаристское решение компилятора, кого инлайнить, а кого нет. В итоге часть методов в объектнике присутствует, часть заинлайнена, потому линковщиком и не находится. Что интересно: явная инстанциация иногда убирает инлайновость и скрывает проблему, но с гарантией проблема скрывается только инстанциацией путем определения функции, аналогичной this_function_is_not_used_anywhere.
Re[5]: Explicit instantiation of template template method
От: Constructor  
Дата: 02.03.18 21:36
Оценка:
Здравствуйте, rg45, Вы писали:

R>Это актуально только для случаев неявного инстанцирования. Для того, чтобы неявное инстанцирование прошло правильно, в точке первого использования должны быть видны и главный шаблон, и все специализации. В рассматриваемом же случае никаких неявных инстанцирований нет, поэтому ошибки подобного рода исключены. В точках использования компилятору достаточно знать только имя, а линкер это имя потом найдет. И отдельного объявления всех специализаций для этого не требуются.


Похоже, что Вы правы. Однако код в этом случае оказывается весьма хрупким с т.з. возникновения ошибок линковки. Стоит перенести общее определение шаблонного метода/функции в заголовочный файл, как начнут возникать непонятные ошибки.
Отредактировано 02.03.2018 21:39 Constructor . Предыдущая версия .
Re[4]: Explicit instantiation of template template method
От: rean  
Дата: 02.03.18 21:48
Оценка:
deleted
Отредактировано 22.04.2019 9:17 deleted2 . Предыдущая версия .
Re[4]: Explicit instantiation of template template method
От: rg45 СССР  
Дата: 02.03.18 21:51
Оценка:
Здравствуйте, ezdoctor, Вы писали:

E>Целью данного инлайна (ныне рудиментарного) было проинструктировать линковщик об отсутствии необходимости ругаться на определение этих методов в двух модулях сразу: раньше эти методы определялись в заголовочном файле, так что (одинаковый) код попадал в два объектника сразу.


Вот поэтму ключевое слово inline лучше указывать при определении функций-членов, а не при объявлении — это если определения даются за пределами класса. А если прямо в теле класса, то вообще ничего не нужно — они автоматически трактуются как inline.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[5]: Explicit instantiation of template template method
От: rg45 СССР  
Дата: 02.03.18 21:57
Оценка: +1
Здравствуйте, rean, Вы писали:

R>Инлайн работает только в пределах одной единицы компиляции. Иначе говоря, в каждом CPP файле будет своя копия, такая же как в другом файле, но своя копия.

R>Поэтому найтись инлайн из одного cpp в другом не может никак.

Ты путаешь inline и функцию с внутренним связыванием (internal linkage, т.е. static). Можешь проверить, обычная inline функция из какого-нибудь пространства имен, не объявленная static, будет имеет один и тот же адрес во всех единицах трансляции.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[6]: Explicit instantiation of template template method
От: ezdoctor  
Дата: 02.03.18 21:57
Оценка:
Здравствуйте, Constructor, Вы писали:

C>Здравствуйте, rg45, Вы писали:


R>>Это актуально только для случаев неявного инстанцирования. Для того, чтобы неявное инстанцирование прошло правильно, в точке первого использования должны быть видны и главный шаблон, и все специализации. В рассматриваемом же случае никаких неявных инстанцирований нет, поэтому ошибки подобного рода исключены. В точках использования компилятору достаточно знать только имя, а линкер это имя потом найдет. И отдельного объявления всех специализаций для этого не требуются.


C>Похоже, что Вы правы. Однако код в этом случае оказывается весьма хрупким с т.з. возникновения ошибок линковки. Стоит перенести общее определение шаблонного метода/функции в заголовочный файл, как начнут возникать непонятные ошибки.


Потому на данный момент рекомендуемой является именно inclusion model, когда все определено в одном заголовке. Я сознательно ушел от этой модели: есть подозрение, что в моем случае бенефитов от ухода больше. Но, как видите, ошибок линковки не избежал.
Re[5]: Explicit instantiation of template template method
От: ezdoctor  
Дата: 02.03.18 22:03
Оценка:
Здравствуйте, rg45, Вы писали:

R>Здравствуйте, ezdoctor, Вы писали:


E>>Целью данного инлайна (ныне рудиментарного) было проинструктировать линковщик об отсутствии необходимости ругаться на определение этих методов в двух модулях сразу: раньше эти методы определялись в заголовочном файле, так что (одинаковый) код попадал в два объектника сразу.


R>Вот поэтму ключевое слово inline лучше указывать при определении функций-членов, а не при объявлении — это если определения даются за пределами класса. А если прямо в теле класса, то вообще ничего не нужно — они автоматически трактуются как inline.


Тут вы не правы. В шаблонных классах объявление метода как inline — это способ избежать проблем при связывании, когда метод оказывается определен в разных модулях. Так что в некоторых случаях есть большая разница от того, объявить метод шаблона как inline или нет.
Re[6]: Explicit instantiation of template template method
От: rean  
Дата: 02.03.18 22:08
Оценка:
deleted
Отредактировано 22.04.2019 9:16 deleted2 . Предыдущая версия .
Re[6]: Explicit instantiation of template template method
От: rg45 СССР  
Дата: 02.03.18 22:15
Оценка:
Здравствуйте, ezdoctor, Вы писали:

E>Тут вы не правы. В шаблонных классах объявление метода как inline — это способ избежать проблем при связывании, когда метод оказывается определен в разных модулях. Так что в некоторых случаях есть большая разница от того, объявить метод шаблона как inline или нет.


Я тебе не предлагаю убрать inline вообще. Просто укажи его не в объявляении, а в определении и все, никаких проблем никогда у тебя не будет. (Ну или покажи какой-нибудь случай, когда возникают проблемы)
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[7]: Explicit instantiation of template template method
От: ezdoctor  
Дата: 02.03.18 22:22
Оценка:
Здравствуйте, rg45, Вы писали:

R>Здравствуйте, ezdoctor, Вы писали:


E>>Тут вы не правы. В шаблонных классах объявление метода как inline — это способ избежать проблем при связывании, когда метод оказывается определен в разных модулях. Так что в некоторых случаях есть большая разница от того, объявить метод шаблона как inline или нет.


R>Я тебе не предлагаю убрать inline вообще. Просто укажи его не в объявляении, а в определении и все, никаких проблем никогда у тебя не будет. (Ну или покажи какой-нибудь случай, когда возникают проблемы)


Ну вот хотя бы мой пример (переносим все в один заголовок и инстанциируем класс одним и тем же типом из двух разных модулей):

//header.h
template<typename T>
class C {
public:
    void memberFunction();

    template <typename S>
    void templateMemberFunction();
};

template<>
void C<int>::memberFunction() {
}

template <>
template <>
void C<int>::templateMemberFunction<double>() {
}

Сравниваем со случаем, когда в определении класса те же методы объявлены как inline:

//header.h
template<typename T>
class C {
public:
    inline void memberFunction();

    template <typename S>
    inline void templateMemberFunction();
};

template<>
void C<int>::memberFunction() {
}

template <>
template <>
void C<int>::templateMemberFunction<double>() {
}


В первом случае возникнет ошибка линковки (линковщик найдет определение шаблонных методов в двух разных модулях), во втором случае этой ошибки с гарантией не возникнет.
Отредактировано 02.03.2018 22:23 ezdoctor . Предыдущая версия .
Re[8]: Explicit instantiation of template template method
От: Constructor  
Дата: 02.03.18 22:28
Оценка:
Здравствуйте, ezdoctor, Вы писали:

E>В первом случае возникнет ошибка линковки (линковщик найдет определение шаблонных методов в двух разных модулях), во втором случае этой ошибки с гарантией не возникнет.


Явные специализации шаблонов — это не шаблоны.
В первом случае Вам надо пометить inline явные специализации методов, точно так же, как Вы бы пометили inline определения обычных методов нешаблонных классов, размещенные в заголовочном файле за пределами определения класса.
Отредактировано 02.03.2018 22:28 Constructor . Предыдущая версия .
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.