Проблема с шаблонным классом

Доброго времени суток!
Прочитал статьи по шаблонам. Решил попробовать. Делаю вроде все как написано в статье. Все хорошо до тех пор, пока весь код в одном файле. Как только разделяю код на несколько файлов перестает собираться. Т.е. компиляция проходит без ошибок, а при сборке не находит функций из шаблонного класса. Вот код:

main.cpp:

#include "cclass.h"

int main() {

    cclass::Class<double> cc;
    cc.size();
}

cclass.h:

#pragma once

namespace cclass {

    template <typename T>
    class Class {
    public:
        Class();
        void size();
        virtual ~Class();
    };
}

cclass.cpp:

#include "cclass.h"
#include <iostream>

using namespace std;

namespace cclass {

    template <typename T>
    Class<T>::Class() { cout << "Class()" << endl; }

    template <typename T>
    void Class<T>::size() {
        cout << sizeof(T) << endl;
    }

    template <typename T>
    Class<T>::~Class() { cout << "~Class()" << endl; }

}

Лог ошибок:

1>------ Build started: Project: MTest, Configuration: Debug Win32 ------
1>  main.cpp
1>  cclass.cpp
1>  Generating Code...
1>main.obj : error LNK2019: unresolved external symbol "public: __thiscall cclass::Class<double>::Class<double>(void)" (??0?$Class@N@cclass@@QAE@XZ) referenced in function _main
1>main.obj : error LNK2019: unresolved external symbol "public: void __thiscall cclass::Class<double>::size(void)" (?size@?$Class@N@cclass@@QAEXXZ) referenced in function _main
1>main.obj : error LNK2019: unresolved external symbol "public: virtual __thiscall cclass::Class<double>::~Class<double>(void)" (??1?$Class@N@cclass@@UAE@XZ) referenced in function _main
1>d:\users data\username\documents\visual studio 2013\Projects\MTest\Debug\MTest.exe : fatal error LNK1120: 3 unresolved externals
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

MS Visual Studio 2015

Не могу понять в чем проблема (((((

Да, так собирается без ошибок. Спасибо.

А сделать так, как описано в статье Раздельная компиляция не получится? В книжках этот момент как-то обходится стороной. И, что характерно, компиляция-то проходит без ошибок. Значит синтаксических ошибок в программе нет. Следовательно при компиляции моего cclass.cpp происходит конкретизация (это так называется?) шаблона под тип double, который используется для шаблона в main. А почему тогда линкер не видит конкретизированные методы? Или это залепуха конкретно линкера от MS?

Администраторы, при добавлении сообщения на форум выскакивает страница Произошла непредвиденная ошибка, однако сообщение добавляется. Это так и должно быть???

И кнопка Предпросмотр работает через раз (((( Сейчас вот показывает Ваше сообщение пусто.

Ждет немного времени?
Наверное, стоит накатать статейку об этом.

Да ждёт конечно. Тем более, что я еще поковырялся в шаблонах и понял, что я в них ничего не понимаю. От слова ва-а-абще (( В книжке вроде все понятно написано. Например, Страутруп. Пишу код по аналогии с тем, как написано в книжке. Не компилируется. Пытаюсь понять невразумительные сообщения компилятора об ошибках, рою доки. В итоге компилируется. Но код существенно отличается от примера Страуструпа:

#include <iostream>

//#include "cclass.h"

namespace cclass {

    using std::cout;
    using std::endl;

    template <typename T> class Class;  // 1 - сначала продекларировать класс

    template <typename T>               // 2 - потом написать функцию
    T get_data(const Class<T>& c) {
        return c.data;
    }

    template <typename T>
    class Class {
    private:
        T data;
    public:
        Class() : data(T(0)) {
            cout << "Class()" << endl;
        }
        void size() {
            cout << sizeof(T) << endl;
        }
        virtual ~Class() {
            cout << "~Class()" << endl;
        }
        friend T get_data<T>(const Class& c); // 3 - объявить функцию другом класса
                                              // 4 - указать, что она параметризована по T
    };
}


using namespace std;

int main() {

    cclass::Class<double> cc;
    cc.size();
    cout << cclass::get_data(cc) << endl;
}

У Страуструпа 1 и 4 просто нет. Описание функции (2) расположено после описания класса. Пруф: раздел C.13.2 по изданию Бьерн Страуструп Язык программирования C++. Специальное издание. Пер. с англ. — М.: Издательство Бином, 2011 г. — 1136 с: ил.

И это я еще не лез в дебри типа С.13.3. Шаблоны в качестве параметров шаблонов.

И это я еще не лез в дебри типа С.13.3. Шаблоны в качестве параметров шаблонов.

Параметры шаблона — это далеко не дебри.
Дебри начинаются от начала SFINAE. )))

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

Хотя Страуструп в 13.7. Организация исходного кода прямым текстом даёт рекомендацию пользоваться именно раздельной трансляцией. Но со Страуструпом, как я писал выше, уже были расхождения. Если честно, я в некоторой растерянности: если уж автору языка нельзя верить, то как вообще изучать этот язык?

В какой-то конкретной ситуации явное инстанцирование наверно спасает, но фактически сводится на нет вся идея шаблонов.

Смотря с какой стороны на это посмотреть.
Была такая фишка, как экспорт шаблонов,
но в силу мазохистской сложности реализации
и не особой надобности была полностью ликвидирована.
И даже ключевое слово export теперь зарезервированно
и мотается без дела, как и register.
Но, вроде как в каком-то из компиляторов
был реализован этот экспорт шаблонов,
но опять же- не срослось.

Для себя сделал вывод: раздельную компиляцию с применением шаблонов делать нельзя.

Фактически — да.

прямым текстом даёт рекомендацию пользоваться именно раздельной трансляцией

Там где это возможно — да.

Если честно, я в некоторой растерянности: если уж автору языка нельзя верить, то как вообще изучать этот язык?

Постоянное изучение, практика,
собственные исследования,
поиск информации в стандарте языка.

Еще вопрос по шаблонам. Можно ли как-то узнать, поддерживает ли тип-параметр какие-то определённые операции? Например,

template <typename T>
class Y {
    T prop;
    // ...
    Y func(T arg) {
        prop = prop * arg;
    }
    // ...
}

если T — числовой тип, проблем нет. А если T что-то нетривиальное, или даже простой bool? Хорошо, если будет достаточно ошибки при компиляции. А если надо в рантайме отловить такую ситуацию и вместо умножения сделать какую-то другую последовательность действий?

И еще вопрос по шаблонам. Я передаю в шаблонную функцию аргумент с типом-параметром T. Если это тривиальный тип, то лучше передавать по значению. Если это сложный тип, то лучше передавать по ссылке. Как это можно реализовать? Т.е. если при инстанцировании тип тривиальный, то передавать по значению, иначе — по ссылке.

Можно ли как-то узнать, поддерживает ли тип-параметр какие-то определённые операции?

Скажем так, что сделать это можно,
но через кучеву кучу костылей и не факт,
что будет полностью переносимо.
Эта тема затрагивается немного,
емнип, в книге Александресску.

Всё еще телятся с введением концептов,
так что в C++17 их тоже не будет.

Можете посмотреть что-то в Boost Concept Check Library,
но сам я ей не пользовался, так что с функционалом не знаком,
может там и нет того, что Вам нужно.

Как это можно реализовать? Т.е. если при инстанцировании тип тривиальный, то передавать по значению, иначе — по ссылке.

Примерно:

template<typename T>
using RefIfNonTrivial = typename std::conditional<
   std::is_trivial<T>::value, 
   T, 
   T&
   >::type;


template<typename T>
void foo(const RefIfNonTrivial<T> x)
{
}

Но всё тоже зависит от конкретных условий.

Спасибо за ответы.
Когнитивный диссонанс во всей красе. Пока читаешь книжки — всё красиво, логично и вообще рульно. Как дело (у меня) до практики доходит — одни костыли и непонятки. Общее впечатление, что все метапрограммирование в C++ пока очень сыро.

По второму ответу — взрыв мозга. Ща отскребу от стен остатки своего серого вещества и полезу смотреть что такое std::conditional %-O

Далеко не сыро. Но не хватает некоторых вещей.
Без них, иногда, приходится костыли изобретать,
например, в виде SFINAE.
Что касается std::conditional, то оно принимает три параметра шаблона, первый из которых bool.
Если первый параметр true, то type будет равен второму параметру, иначе — третьему.

«Не хватает некоторых вещей» — это сыро и недоработано. Писать код на шаблонах — как гулять по болоту: есть тропинка, отмеченная вешками; шаг влево, шаг вправо — топь и бездна. С одной стороны шаблонный код должен быть универсальным (по определению), а с другой стороны не дай Бог в него «не тот» тип подсунут. Даже STL этим грешит.

По сути, в старый добрый C++ встроили ещё один язык с жутким синтаксисом, неочевидной семантикой и, вдобавок, реализующий другую парадигму программирования.

«Не хватает некоторых вещей» — это сыро и недоработано.

Я еще не видел ничего, где бы хватало всего.
А значит нет ничего не сырого? )))
Для любой технологии, фреймворка, языка,
можно назвать вещи, которых не хватает,
но это не делает его недоработанной или сырой.
Учесть всё и на все случаи жизни просто невозможно.
Или Вы один могёте?

Писать код на шаблонах — как гулять по болоту: есть тропинка, отмеченная вешками; шаг влево, шаг вправо — топь и бездна.

Если не разбираться в шаблонах — да.
В остальном же, все эти тропы всем
хорошо известны и нанесены на карту.

а с другой стороны не дай Бог в него «не тот» тип подсунут.

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

старый добрый C++

Это в какой?
Тот нестандартизированный мусор,
который каждый делал как мог?
А шаблоны в языке с самого первого стандарта.

с жутким синтаксисом

Местами такое имеется.
Но, прокачав скилл в шаблонах становится легче.
В любой профессии так — пока ты нуб, тебе ничего не понятно. )))

Для любой технологии, фреймворка, языка, можно назвать вещи, которых не хватает, но это не делает его недоработанной или сырой.

Угу. Вопрос только в том как много тех вещей, которых не хватает. И в том, на сколько удобно пользоваться тем, что реализовано.

Если не разбираться в шаблонах — да.

Вот честно скажу, с шаблонами у меня хуже, чем хотелось бы. Не нравится мне как реализована эта фича ((

а с другой стороны не дай Бог в него «не тот» тип подсунут.

Для этого делают нужные проверки. Плюс имеется документация.

Читал твои ответы Мартыну как раз на счёт проверок. Всё очень интуитивно и логично. Особенно порадовал совет обратиться к сторонней библиотеке.

Наличие документации — это хорошо. Плохо, когда после двух абзацев описания функциональности идёт две страницы комментариев по ограничениям и «особым» случаям.

Всем хорошо электричество, но не дай Бог убьет.

При этом ни кто постоянно не носит резиновые перчатки и не подкладывает под ноги сухой резиновый коврик, когда пользуется электричеством. В этом-то и разница.

старый добрый C++

Это в какой? Тот нестандартизированный мусор, который каждый делал как мог?

Во-первых, стандарты были начиная с 1998 года. Во-вторых, на C++ за последние 20 лет написана куева туча очень неплохого софта. Так что это ты зря.

Особенно порадовал совет обратиться к сторонней библиотеке.

А Вы всё всегда сами велосипедите?
Обращение к сторонним библиотекам — нормальная практика.
На то они и создавались изначально.
А в стандартной библиотеке этот функционал еще в разработке.
Но даже если сделают, всё равно всем не угодишь.

и не подкладывает под ноги сухой резиновый коврик, когда пользуется электричеством

Вот поэтому у нас на работе уже не первого электрика калечит...
Ведь именно они лезут во внутрь.
А пользоваться тем, что есть по правилам можно и не зная шаблонов.

Во-первых, стандарты были начиная с 1998 года.

угу, в котором сразу же были шаблоны.

Во-вторых, на C++ за последние 20 лет написана куева туча очень неплохого софта.

Чистый C++ — это то, что написано на C++.
C++ стандартизированный язык.
Первый стандарт вышел в 1998 году.
В нем сразу были шаблоны.
Так что шаблоны — часть чистого c++.

Причем здесь 20 лет и софт — не ясно.

Предлагаю закончить.
Кому-то нравится, кому-то нет.
Спорить бессмысленно.

Внимание! Это довольно старый топик, посты в него не попадут в новые, и их никто не увидит. Пишите пост, если хотите просто дополнить топик, а чтобы задать новый вопрос — начните новый.

Ответить

Вы можете использовать разметку markdown для оформления комментариев и постов. Используйте функцию предпросмотра для проверки корректности разметки.

Пожалуйста, оформляйте исходный код в соответствии с правилами разметки. Для того, чтобы вставить код в комментарий, скопируйте его в текстовое поле ниже, после чего выделите то, что скопировали и нажмите кнопку «код» в панели инструментов. Иначе ваш код может принять нечитаемый вид.

Либо производите оформление кода вручную, следующим образом:

``` #include <iostream> using namespace std; int main() { // ... } ```

Предпросмотр сообщения

Ваше сообщение пусто.