Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.236.100.210] |
|
Сообщ.
#1
,
|
|
|
Всем привет!
Решил я немного пометапрограммировать, вернее попытаться ... но произошел горкий катаклизьм! Суть в следующем. Делаю тестовую программу main.cpp: #include <iostream> #include "anydigit.h" using namespace std; int main() { AnyDigit any(3.14); cout << "Any: " << any << endl; return 0; } А в anydigit.h пишу следующее: #ifndef ANYDIGIT_H #define ANYDIGIT_H #include <ostream> class AnyDigit { double _value; public: AnyDigit(): AnyDigit(0.0) {}; template<typename T> AnyDigit(const T value) { static_assert( (std::is_integral_v<T> || std::is_floating_point<T>::value), "Желаю аргумент в виде целого или числа с плавающей!" ); _value = value; }; friend std::ostream& operator<<(std::ostream& os, const AnyDigit& obj) { os << std::scientific << obj._value; return os; }; }; #endif // ANYDIGIT_H И все собирается, и запускается, и работает. Но как только я хочу в заголовочном файле оставить только объявление: template<typename T> AnyDigit(const T value); А реализацию перенести в .cpp файл: #include "anydigit.h" template<typename T> AnyDigit::AnyDigit(const T value) { static_assert( (std::is_integral_v<T> || std::is_floating_point<T>::value), "Желаю аргумент в виде целого или числа с плавающей!" ); _value = value; } То сборка происходит с ошибкой: ld.lld: error: undefined symbol: AnyDigit::AnyDigit<double>(double) >>> referenced by ..\../main.cpp:7 >>> debug/main.o:(_main) clang++: error: linker command failed with exit code 1 (use -v to see invocation) mingw32-make[1]: *** [Makefile.Debug:71: debug/AnyDigit.exe] Error 1 Ну и, собственно, вопрос: что я делаю не так, и как это сделать правильно? Я имею ввиду разнесение кода на объявление и реализацию в разные файлы при реализации шаблонного конструктора (возможно и методов - в будущем). |
Сообщ.
#2
,
|
|
|
Определение шаблона должно быть видно компилятору в момент инстанцирования. Поскольку вы унесли его в другой .cpp - компилятор его не видит и понятия не имеет, как именно ему инстанцировать шаблон. Оставьте реализацию в заголовочном файле. Если сомневаетесь - посмотрите любой системный заголовочный файл.
Добавлено: в .cpp можно (нужно) уносить полную специализацию шаблона. Т.е. если для AnyDigit<uint8_t> у вас будет какая-то особая реализация конструктора - в заголовочном вы пишете template<> AnyDigit<uint8_t>(const uint8_t value); template<> AnyDigit<uint8_t>(const uint8_t value) : _value(value) { std::cout<<"Слава мне, победителю драконов!"; } |
Сообщ.
#3
,
|
|
|
Цитата Dushevny @ Определение шаблона должно быть видно компилятору в момент инстанцирования. Поскольку вы унесли его в другой .cpp - компилятор его не видит и понятия не имеет, как именно ему инстанцировать шаблон. Оставьте реализацию в заголовочном файле. Если сомневаетесь - посмотрите любой системный заголовочный файл. Спасибо! Да нет, уже не сомневаюсь. Догадывался об этом, но думал, что я где-то в чём-то ошибаюсь, упускаю и есть более другие решения. |
Сообщ.
#4
,
|
|
|
Цитата Dushevny @ Не совсем так. Компилятор не конкретизирует шаблон, если не будет видеть его определения, но это не значит, что программа неверна. Как и в случае обычных функций, компилятору достаточно объявления, чтобы проверить корректность использования, при необходимости скастить параметры, сгенерировать код вызова и понять, что и как делать с результатом.Определение шаблона должно быть видно компилятору в момент инстанцирования. Поскольку вы унесли его в другой .cpp - компилятор его не видит и понятия не имеет, как именно ему инстанцировать шаблон. Проблема в том, что компилятор – как и в случае обычных функций – полагает, что где-то это определение есть, и если оно есть, то ок. Только вот с шаблонами часто получается так, что его нет. Когда где-то там, компилятор встретит определение шаблона, он понятия не имеет, где и с какими наборами параметров он был использован. Получается, что в точке использования нет определения, поэтому компилер не в состоянии сгенерирвоать конкретизацию и вставляет просто вызов инстанцированного шаблона, а в точке, где доступно определение, обычно нет использования с теми же наборами параметров, поэтому компилер этой конкретизации не сгенерировал. В итоге линкер ругается на отсутствующий символ. Проблема решатся тремя путями. Самый правильный – экспорт шаблонов. Увы, но ленивые разработчики реализаций по всему миру саботировали эту фичу, и единственные, кто её в своё время реализовал ≈20 лет назад – это EDS, известная своими front-end решениями для других. Например, Intel C++ Compiler юзает именно их. Правда, экспорт шаблонов они лицензировали у них только под линух. Из-за ленности разработчиков Комитет в C++11 объявил экспорт шаблонов опциональной фичей. Второй по популярности – явные инстанцирования. Там, где доступно определение, можно нафигачить явных запросов на инстанцирование, с теми же наборами параметров, с каковыми шаблон используется где-то там. В итоге компилятор нагенерит инстанцирований без неявных запросов, и у линкера всё срастётся. Ну и третий самый дешёвый для нас, но не компилятора, каковой практически повсеместно и используется – предоставлять компилятору определение всегда, чтобы он мог генерировать конкретизации везде, где они неявно запрашиваются, а проблемы с дубликатами, каковых в итоге по всей программе может оказаться неимоверное количество, становятся проблемами компилятора и линкера, которые они обязаны решить самостоятельно. |
Сообщ.
#5
,
|
|
|
Ничего не понял, но очень интересно! Как-то в юности один из моих преподов прямо вбил мне в мозг фразу "Не учи меня жить - помоги материально!". Конечно всё в переносном смысле, а если в прямом "Хватит объяснений - скажи чего делать-та!". Qraizer, собственно и у меня вопрос - реализовывать тела шаблонных конструкторов и методов прямо в заголовочном файле - это единственный выход, или где?
Добавлено Цитата Dushevny @ Поскольку вы унесли его в другой .cpp - компилятор его не видит и понятия не имеет, как именно ему инстанцировать шаблон. И, кстати да, утверждение не совсем верное - не компилятор ругается, а линкер. К тому времени компилятор уже давно и умело покинул оперативную память. Добавлено Цитата Dushevny @ Определение шаблона должно быть видно компилятору в момент инстанцирования. Поскольку вы унесли его в другой .cpp - компилятор его не видит и понятия не имеет, как именно ему инстанцировать шаблон. Оставьте реализацию в заголовочном файле. Если сомневаетесь - посмотрите любой системный заголовочный файл. Добавлено: в .cpp можно (нужно) уносить полную специализацию шаблона. Т.е. если для AnyDigit<uint8_t> у вас будет какая-то особая реализация конструктора - в заголовочном вы пишете Вот тут и засада. Я и без шаблонов могу нагенерить 100500 конструкторов с помощью нехитрого скрипта на ... Perl'овке! Как два байта об асфальт Но смысл тогда в метапрограммировании (конкретно в моем случае)?!! |
Сообщ.
#6
,
|
|
|
Цитата Majestio @ Чуть подробнее. Например:Ничего не понял, но очень интересно! // q.h template <typename T> T add(const T& a, const T& b); // q1.cpp #include "q.h" template <typename T> T add(const T& a, const T& b) { return a + b; } // q2.cpp #include <iostream> #include "q.h" int main() { std::cout << add(12, 34) << std::endl; } q2.obj : error LNK2019: ссылка на неразрешенный внешний символ "int __cdecl add<int>(int const &,int const &)" (??$add@H@@YAHABH0@Z) в функции _main. q1.exe : fatal error LNK1120: неразрешенных внешних элементов: 1 Решаем через экспорт шаблонов: // q.h export template <typename T> T add(const T& a, const T& b); D:\Work\delMe\q.h(2): error C3348: экспортированные шаблоны не являются частью текущих стандартов C++ Цитата Working Draft, Standard for Programming Language C++ C.2.7 Clause 17: templates [diff.cpp03.temp] 1 Affected subclause: 17.1 Change: Remove export. Rationale: No implementation consensus. Effect on original feature: A valid C++ 2003 declaration containing export is ill-formed in this International Standard. Ок, решаем через явную конкретизацию: // q1.cpp #include "q.h" template <typename T> T add(const T& a, const T& b) { return a + b; } template int add<int>(const int&, const int&); /out:q1.exe q1.obj q2.obj D:\Work\delMe>q1 46 template <typename T> T add(const T& a, const T& b) { return f(a) + f(b); } Резюме. Третий вариант с реализацией сразу в q.h и без всяких q1.cpp нынче рулит. В отношении твоего вопроса о метакодинге, получается, что второй вариант тебе просто бесполезен, т.к. ты и так видишь, какие конкретизации явно запрашиваешь. Ну и т.к. первый вариант недоступен, то да, остаётся исключительно третий. |
Сообщ.
#7
,
|
|
|
Цитата Qraizer @ Резюме. Третий вариант с реализацией сразу в q.h и без всяких q1.cpp нынче рулит. Н-да ... делать нечего, надо принимать "правила игры" |