Избегайте накопления типов итераторов в аргументах шаблона - PullRequest
0 голосов
/ 22 ноября 2018

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

Рассмотрим класс, в котором есть n методы, и каждый из них возвращает некоторую коллекцию.Это означает, что мне нужно создать класс с n параметрами шаблона (выходные итераторы).Число параметров шаблона начнет расти, и я не знаю, как справиться с этой проблемой.

Конкретный пример:

template<class TNode, class TEdge> class AGraph;
template<class TNode, class TEdge, class OutputOfFunc1, class OutputOfFunc2>
class APathCalculation
{
    using TGraph = AGraph<TNode, TEdge>;
public:
    virtual void ReturnShortestPath(size_t source, size_t dest, TGraph& graph, OutputOfFunc1 outPath) = 0;//func1
    virtual void ReturnAllShortestDistances(size_t source, TGraph& graph, OutputOfFunc2 outDistances) = 0;//func2
};

И я получу различные классы (например, DijkstraБеллман-Форд) от APathCalculation.Но проблема в том, что я ввел шаблонные аргументы

...class OutputOfFunc1, class OutputOfFunc2>

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

В настоящее время я объявляю класс какэто

// Example of declaration
APathCalculation<
    int,    // type of node
    double, // type of edge
    back_insert_iterator<list<size_t>>,  // return type of shortest path between two nodes
    back_insert_iterator<vector<double>> // return type of shortest distances from source node
> &pathCalculator;  

Ответы [ 4 ]

0 голосов
/ 22 ноября 2018

«поверхностный» ответ:

Чтобы избежать N шаблонных параметров в одном классе, необходимо разбить его на N классов с 1 шаблонным параметром в каждом.

A «глубокий»ответ:

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

Если вам действительно нужен динамический полиморфизм, вам нужен класс динамически полиморфного итератора.

На самом деле вы можете использовать и то, и другое (статически полиморфный шаблон специализации для общих алгоритмов и / или общих итераторов, подкрепленный регистром «по умолчанию»)это оболочка для динамически полиморфных реализаций), но это, вероятно, слишком сложно для вашей задачи.Кроме того, вам все равно понадобится способ привязать вашу оболочку «по умолчанию» к реальному алгоритму, который вы хотите, чтобы клиент APathCalculation вызывал.

Лично я бы начал с чисто основанного на шаблонах решения, но я мог быпонять кого-то, начиная с решения, основанного исключительно на виртуальных функциях.

0 голосов
/ 22 ноября 2018

Вот три альтернативных варианта:

Вариант 1. Просто выберите тип и верните его

Если вы беспокоитесь о производительности, это скорее всего неЭто почти так же плохо, как вы думаете.

virtual std::vector<TEdge> FindShortestPath(size_t source, size_t dest, TGraph& graph) = 0;

Вариант 2: Принять обратный вызов

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

virtual void TraverseShortestPath(
 size_t source,
 size_t dest,
 TGraph& graph,
 std::function<void(TEdge*)> callback) = 0;

Вариант 3: использовать шаблон функции

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

template <class TGraph, class OutIt>
void FindShortestPath(size_t source, size_t dest, TGraph& graph, OutIt output)
{
  // details...
}

Конечно, вы можете сделать много вариантов этих подходов.Я бы также предостерег от использования итераторов вывода, подобных этому.Вызывающая сторона может передавать что-то безопасное, например back_inserter, но может также передавать что-то опасное, например, необработанный указатель, что может легко привести к переполнению буфера.

0 голосов
/ 22 ноября 2018

Вы можете определить все свои аргументы в одной структуре и использовать их во всех ваших структурах, например:

template<class TNode, class TEdge> class AGraph;

template< typename TYPE_DEFS >
class APathCalculation
{
    using TGraph = AGraph<typename TYPE_DEFS::TNode, typename TYPE_DEFS::TEdge>;
public:
    virtual void ReturnShortestPath(size_t source, size_t dest, TGraph& graph, typename TYPE_DEFS::OutputOfFunc1 outPath) = 0;//func1
    virtual void ReturnAllShortestDistances(size_t source, TGraph& graph, typename TYPE_DEFS::OutputOfFunc2 outDistances) = 0;//func2
};


template< typename TYPE_DEFS > 
class Dijkstra: public APathCalculation<TYPE_DEFS>
{
    using TGraph = AGraph<typename TYPE_DEFS::TNode, typename TYPE_DEFS::TEdge>;
public:
    virtual void ReturnShortestPath(size_t source, size_t dest, TGraph& graph, typename TYPE_DEFS::OutputOfFunc1 outPath) override {}
    virtual void ReturnAllShortestDistances(size_t source, TGraph& graph, typename TYPE_DEFS::OutputOfFunc2 outDistances) override {}
};

struct TypeDefs
{
    using TNode = int;
    using TEdge = double;

    using OutputOfFunc1 = std::back_insert_iterator<std::list<size_t>>;
    using OutputOfFunc2 = std::back_insert_iterator<std::vector<double>>;
};

int main()
{
    Dijkstra<TypeDefs> d;
}

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

0 голосов
/ 22 ноября 2018

"Рассмотрим класс, который имеет n методов, и каждый возвращает некоторую коллекцию. Это означает, что мне нужно создать класс с n параметрами шаблона (выходные итераторы).

Нет, вынет. Вы действительно можете создать класс с параметрами шаблона 0. Однако у каждого метода есть один параметр шаблона. В вашем случае вы можете уменьшить его до 2 параметров шаблона для класса:

template<class TNode, class TEdge>
class APathCalculation
{
    using TGraph = AGraph<TNode, TEdge>;
public:
    template<class OutputOfFunc1>
    void ReturnShortestPath(size_t source, size_t dest, TGraph& graph, OutputOfFunc1 outPath);

    template<class OutputOfFunc2>
    void ReturnAllShortestDistances(size_t source, TGraph& graph, OutputOfFunc2 outDistances);
};

Обратите внимание, что я внес одно важное изменение: это класс в ОО-смысле. У вас был абстрактный класс или интерфейс . Абстрактные классы - хороший способдля разъединения вызывающего и вызываемого абонентов, но здесь вы не можете разъединить их: вызывающий и вызываемый должны согласовать типы итераторов.

...