Как отделить определение от реализации?
Есть класс:
template <typename Vertex, typename Edge>
class AbstractExecuter
{
protected:
Graph <Vertex, Edge> *graph = nullptr;
public:
AbstractExecuter(Graph <Vertex, Edge>* graph);
AbstractExecuter(Graph <Vertex, Edge>* graph, int V, bool D);
AbstractExecuter(Graph <Vertex, Edge>* graph, int V, int E, bool D);
~AbstractExecuter();
virtual void InsertV() = 0;
virtual bool DeleteV(Vertex& vertex) = 0;
virtual void InsertE(Vertex& vertex1, Vertex& vertex2) = 0;
virtual bool DeleteE(Vertex& vertex1, Vertex& vertex2) = 0;
virtual Edge& GetEdge(Vertex& vertex1, Vertex& vertex2) = 0;
virtual void print() = 0;
};
Хочу написать реализацию конструкторов, но появляется ошибка:
template<typename Vertex, typename Edge>
inline AbstractExecuter<Vertex, Edge>::AbstractExecuter(Graph<Vertex, Edge>* graph) //ошибка
{
}
Вот так выглядит сообщение об ошибке:

Опытным путём стало понятно, что конкретно не нравится объявление Graph<Vertex, Edge>* graph.
Поэтому было решено попробовать написать реализацию прям под определением (прям в классе). И это решило проблему.
Но всё равно хотелось бы как-то вернуться к варианту с отдельным определением и реализацией. Кажется, что проблема в параметрах шаблона, но не понятно какая.
Определение и реализация в одном файле.
Воспроизводимый пример:
первый файл
#include "Graph.h"
template <typename Vertex, typename Edge>
class AbstractExecuter
{
protected:
Graph <Vertex, Edge> *graph = nullptr;
public:
AbstractExecuter(Graph <Vertex, Edge>* graph);
AbstractExecuter(Graph <Vertex, Edge>* graph, int V, bool D);
AbstractExecuter(Graph <Vertex, Edge>* graph, int V, int E, bool D);
~AbstractExecuter();
virtual void InsertV() = 0;
virtual bool DeleteV(Vertex& vertex) = 0;
virtual void InsertE(Vertex& vertex1, Vertex& vertex2) = 0;
virtual bool DeleteE(Vertex& vertex1, Vertex& vertex2) = 0;
virtual Edge& GetEdge(Vertex& vertex1, Vertex& vertex2) = 0;
virtual void print() = 0;
};
template<typename Vertex, typename Edge>
inline AbstractExecuter<Vertex, Edge>::AbstractExecuter(Graph<Vertex, Edge>* graph)
{
}
второй файл
#include "AbstractExecuter.h"
template <typename Vertex, typename Edge>
class Graph
{
private:
AbstractExecuter <Vertex, Edge> *executer = nullptr; //исполнитель (либо для L-графа, либо для M-графа)
std::vector <Vertex> vertex; //вектор вершин
std::vector < std::vector <Edge> > matrix; //матрица смежности
std::vector < std::vector <Edge> > list; //списки смежности
int v = 0; //количество вершин
int e = 0; //количество рёбер
bool direct = false; //ориентированность (false - неориентировванный, true - ориентированный)
bool form = false; //форма представления (false - L-граф, true - M-граф)
public:
Graph();
Graph(int V, bool D, bool F);
Graph(int V, int E, bool D, bool F);
Graph(const Graph& graph);
~Graph();
int V();
int E();
bool Direct();
bool Dense();
double K();
bool ToMatrixGraph();
bool ToListGraph();
void InsertV();
bool DeleteV(Vertex& vertex);
void InsertE(Vertex& vertex1, Vertex& vertex2);
bool DeleteE(Vertex& vertex1, Vertex& vertex2);
Edge& GetEdge(Vertex& vertex1, Vertex& vertex2);
void print();
};
template<typename Vertex, typename Edge>
inline Graph<Vertex, Edge>::Graph()
{
executer = new LExecuter<Vertex, Edge>(this);
}
template<typename Vertex, typename Edge>
inline Graph<Vertex, Edge>::Graph(int V, bool D, bool F)
{
if (F) {
executer = new MExecuter<Vertex, Edge>(this, int V, bool D);
}
else {
executer = new LExecuter<Vertex, Edge>(this, int V, bool D);
}
}
template<typename Vertex, typename Edge>
inline Graph<Vertex, Edge>::Graph(int V, int E, bool D, bool F)
{
if (F) {
executer = new MExecuter<Vertex, Edge>(this, int V, int E, bool D);
}
else {
executer = new LExecuter<Vertex, Edge>(this, int V, int E, bool D);
}
}
template<typename Vertex, typename Edge>
inline int Graph<Vertex, Edge>::V()
{
return v;
}
template<typename Vertex, typename Edge>
inline int Graph<Vertex, Edge>::E()
{
return e;
}
template<typename Vertex, typename Edge>
inline bool Graph<Vertex, Edge>::Direct()
{
return direct;
}
template<typename Vertex, typename Edge>
inline bool Graph<Vertex, Edge>::Dense()
{
return form;
}
template<typename Vertex, typename Edge>
inline double Graph<Vertex, Edge>::K()
{
if (direct) {
return double(v * (v - 1)) / (2 * e);
}
else {
return double(v * (v - 1)) / e;
}
}
template<typename Vertex, typename Edge>
inline void Graph<Vertex, Edge>::print()
{
executer->print();
}
template<typename Vertex, typename Edge>
inline void Graph<Vertex, Edge>::InsertV()
{
executer->InsertV();
}
template<typename Vertex, typename Edge>
inline bool Graph<Vertex, Edge>::DeleteV(Vertex& vertex)
{
return executer->DeleteV(vertex);
}
template<typename Vertex, typename Edge>
inline void Graph<Vertex, Edge>::InsertE(Vertex& vertex1, Vertex& vertex2)
{
executer->InsertE(vertex1, vertex2);
}
template<typename Vertex, typename Edge>
inline bool Graph<Vertex, Edge>::DeleteE(Vertex& vertex1, Vertex& vertex2)
{
return executer->DeleteE(vertex1, vertex2);
}
template<typename Vertex, typename Edge>
inline Edge& Graph<Vertex, Edge>::GetEdge(Vertex& vertex1, Vertex& vertex2)
{
return executer->GetEdge(vertex1, vertex2);
}
Ответы (1 шт):
Переформулирую Ваш вопрос, "Есть шаблонный класс". А для шаблонного класса подобное разделение сильно усложненно, поэтому, легче считать, что так не стоит делать. И это очевидно - ведь в момент использования такого класса, компилятору нужно будет сгенерировать тело функций по конкретному типу. А тела нет, оно где то там в cpp файле. С другой стороны, когда компилятор возьмется компилировать cpp файл, то вдруг обнаружит, что он не может скомпилировать функцию, так как он не знает реальных типов. Не бегать же ему по всему "проекту" (а в плюсах пока нет такого понятия!) и искать потенциальных кандидатов. А если это прекомпилированная библиотека? Телепанию ещё не подвезли в с++.
Сходный вопрос на enSO
В таких языках как Java это делается легко, так как там шаблоны (дженерики) являются только синтаксическим сахаром, а внутри просто, очень грубо говоря, оперируют void* (точнее - object).