Я пытаюсь написать методы в стиле LINQ для моего базового класса Iterator, от которого наследуются List и Sequence, но эти два контейнера будут иметь свои собственные реализации этих методов. Метод «Где» был довольно простым. Метод «Выбрать» очень сложен; у вас не может быть виртуальных шаблонных методов.
template <typename T>
class Iterator {
public:
virtual ~Iterator() {};
// This is illegal, but if it weren't, it would be the functionality I want.
template <typename R>
virtual shared_ptr<IIterator<R>> Select(const function<R(T)>& func) = 0;
virtual shared_ptr<IIterator<T>> Where(const function<bool(T)>& func) = 0;
};
«Выбрать» позволит вам преобразовать итератор типа «сэндвич с ветчиной» в итератор типа «салат», например.
HamSandwiches-> Выбрать<‘Салат’> ([] (shared_ptr<‘HamSandwich’> hs) {return hs-> Салат; });
Игнорировать одинарные кавычки.
Поскольку у нас не может быть виртуальных шаблонных функций, я бы, конечно, не сделал эту функцию виртуальной. В этом случае у нас есть простая старая функция, которую мы никогда не должны «скрывать» ее реализацию, записывая реализации в List и Sequence по сравнению с виртуальной функцией; это будет считаться недостатком дизайна.
template <typename T>
class Iterator {
public:
virtual ~Iterator() {};
template <typename R>
shared_ptr<Iterator<R>> Select(const function<R(T)>& func);
virtual shared_ptr<Iterator<T>> Where(const function<bool(T)>& func) = 0;
};
template <typename T>
template <typename R>
shared_ptr<Iterator<R>> Iterator<T>::Select(const function<R(T)>& func) {
//Implementation - What would it be?
}
Теперь у нас должна быть реализация в нашем базовом классе, которая должна быть несколько специфичной для List и Sequence. Из того, что я видел, вы начинаете создавать защищенные «функции реализации» для выполнения определенных действий в «Select», которые могут быть переопределены List или Sequence.
Я не ищу точного ответа здесь, я ищу что-то, что помогло бы мне достичь того, что я могу / должен хотеть быть. Кто-нибудь замечает какие-то распространенные ошибки или вещи, с которыми я могу ошибаться?
Идеи по реализации LINQ в C ++, которые я видел, совсем не основывались на виртуальных методах. Вместо этого каждый результат был возвращен в класс шаблона, примерно так:
template <class T>
class RangeWrapper
{
public:
template <class U>
Select(U u) -> decltype(...) {
return RangeWrapper<SelectRange, U>(_myRange, u);
}
private:
T& _myRange;
};
Если вы соедините несколько таких цепочек, тип возвращаемого значения может стать довольно большим, но это цена, которую нужно заплатить, чтобы все было сделано во время компиляции.
Вы можете реализовать стирание типа, чтобы всегда возвращать итератор типа Iterator<T>
, Этого должно быть довольно легко достичь с помощью библиотеки итераторов со стертыми типами в Интернете (есть много, вы можете взглянуть на boost.TypeErasure, который принят, но еще не выпущен).
В качестве альтернативы вы можете использовать any_range в boost, если вы в порядке для работы с диапазонами (они отображаются на LINQ более естественно, чем итераторы).
Если вы не делаете это в качестве учебного упражнения, есть несколько решений, которые уже реализованы. Используйте Google. Следует отметить, что Microsoft сама работает над C ++ Linq для реализации реактивных расширений поверх него..
Других решений пока нет …