2017-01-08 2 views
3

Я знаю, что нельзя использовать шаблоны для виртуальных методов в C++ (или наоборот), как, например, обсуждались here и here. К сожалению, я не уверен, как справиться с этим ограничением в моем случае.Наличие виртуальных методов в качестве шаблонов, которые принимают итераторы

У нас есть шаблон класса, который включает в себя шаблон метода:

template <class T> 
class BeliefSet : public Belief<T> 
{ 
private: 
    std::vector<T> m_Facts; 

public: 
    template <class Iter> 
    void SetFacts(Iter IterBegin, Iter IterEnd, bool Append = false) 
    { 
     if(!Append) 
     { 
      m_Facts.clear(); 
     } 
     m_Facts.insert(m_Facts.end(), IterBegin, IterEnd); 
    } 
}; 

Метод SetFacts() должен иметь возможность получить два входных итераторы для контейнера STL, поэтому мы используем шаблон метода здесь.

Теперь я хотел бы сделать этот метод SetFacts() виртуальным, что невозможно в C++, как этот код написан на данный момент. Так что же еще типичный подход для решения этой ситуации?

+1

Что вы пытаетесь достичь? Звучит как проблема XY –

+0

@VittorioRomeo Я хочу предоставить функцию 'virtual', которая должна позволять добавлять mutliple значения контейнера STL во внутренний вектор' m_Facts'. – Matthias

ответ

4

что-то на основе CRTP идиомы и немного SFINAE, вероятно, может решить:

template <class D, class T> 
class BeliefSet : public Belief<T> 
{ 
private: 
    std::vector<T> m_Facts; 

    template <class Iter> 
    void SetFactsInternal(char, Iter IterBegin, Iter IterEnd, bool Append = false) 
    { 
     if(!Append) 
     { 
      m_Facts.clear(); 
     } 
     m_Facts.insert(m_Facts.end(), IterBegin, IterEnd); 
    } 

    template <class Iter, typename U = D> 
    auto SetFactsInternal(int, Iter IterBegin, Iter IterEnd, bool Append = false) 
    -> decltype(static_cast<U*>(this)->OverloadedSetFacts(IterBegin, IterEnd, Append), void()) 
    { 
     static_cast<U*>(this)->OverloadedSetFacts(IterBegin, IterEnd, Append); 
    } 

public: 
    template <typename... Args> 
    void SetFacts(Args&&... args) 
    { 
     SetFactsInternal(0, std::forward<Args>(args)...); 
    } 
}; 

Ваш производный класс может реализовать функцию OverloadedSetFacts члены перегрузкиSetFacts.
Кроме того, производный класс должен наследовать от BeliefSet как это следующим образом:

struct Derived: BeliefSet<Derived, MyTType> 
{ 
    //... 
}; 

Это является ключевой концепцией позади CRTP идиомы в конце концов.


вытекает минимальный, рабочий пример (в C++ 14 для простоты):

#include<iostream> 

template <class D> 
class Base { 
private: 
    template <class C> 
    auto internal(char, C) { 
     std::cout << "internal" << std::endl; 
    } 

    template <class C, typename U = D> 
    auto internal(int, C c) 
    -> decltype(static_cast<U*>(this)->overloaded(c), void()) { 
     static_cast<U*>(this)->overloaded(c); 
    } 

public: 
    template <typename... Args> 
    auto base(Args&&... args) { 
     internal(0, std::forward<Args>(args)...); 
    } 
}; 

struct Overloaded: Base<Overloaded> { 
    template<typename T> 
    auto overloaded(T) { 
     std::cout << "overloaded" << std::endl; 
    } 
}; 

struct Derived: Base<Derived> {}; 

int main() { 
    Overloaded over; 
    over.base(0); 
    Derived der; 
    der.base(0); 
} 

Как вы можете видеть, вы можете обеспечить реализацию по умолчанию в вашем базовом классе и переопределения он в производном классе, если необходимо.

Посмотрите на wandbox.

3

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

Например, стирание вниз для каждого - T.

template<class T> 
using sink=std::function<void(T)>; 
template<class T> 
using for_each_of=sink< sink<T> const& >; 

Сейчас в Belief<T> мы создали две вещи:

template<class T> 
struct Belief{ 
    template <class Iter> 
    void SetFacts(Iter IterBegin, Iter IterEnd, bool Append = false){ 
     SetFactsEx(
     [&](sink<T const&> const& f){ 
      for(auto it=IterBegin; it != IterEnd; ++it) { 
      f(*it); 
     }, 
     Append 
    ); 
    } 
    virtual void SetFactsEx(for_each_of<T const&> elems, bool Append = false)=0; 
}; 

Теперь мы делаем это в производном классе:

virtual void SetFactsEx(for_each_of<T const&> elems, bool Append = false) override 
{ 
    if(!Append) 
     m_Facts.clear(); 
    elems([&](T const& e){ 
     m_Facts.push_back(e); 
    }); 
} 

И мы сделали.

Здесь есть потеря эффективности, но вы можете продолжать обогащать интерфейс стирания типа, чтобы уменьшить его. Всюду до стирания «вставить в конец вектора» для почти нулевой потери эффективности.

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

+0

Вы хотите вызвать 'SetFactsEx' внутри тела' SetFacts', не так ли? –

+0

@paolo исправлен! Изначально было одно и то же имя, но вызов SetFacts из указателя на производный вызывал проблемы с перегрузкой и пропустил это имя chamge. – Yakk

1

Другого solutionбудет типом стирания входных итераторов стандартной библиотеки с определенным пользователем TypeErasedIterator. Таким образом, вы можете превратить любой итератор ввода в объект TypeErasedIterator, и интерфейсы ваших классов должны иметь дело только с этим типом.

#include <memory> 
#include <iterator> 
#include <functional> 

// ----- (minimal) input iterator interface ----- // 
template<typename T> 
struct BaseIterator { 
    virtual T& operator*() = 0; 
    virtual BaseIterator& operator++() = 0; 
    virtual bool operator!=(const BaseIterator& it) const = 0; 
}; 

// ----- templatized derived iterator ----- // 
template<typename T, typename It> 
class InputIterator : public BaseIterator<T> { 
    It it_; 
public:  
    InputIterator(It it) : it_{it} {} 
    typename It::value_type& operator*() override { return *it_; } 
    InputIterator& operator++() override { ++it_; return *this; } 
    bool operator!=(const BaseIterator<T>& it) const override 
    { 
     auto ptr = dynamic_cast<const InputIterator<T, It>*>(&it); 
     if(!ptr) return true; 
     return it_ != ptr->it_; 
    } 
}; 

// ----- type erased input iterator ----- // 
template<typename T> 
class TypeErasedIterator { 
    std::unique_ptr<BaseIterator<T>> it_; 
    std::function<std::unique_ptr<BaseIterator<T>>()> copy_; // for implementing the copy ctor 
public: 
    template<typename It> 
    TypeErasedIterator(It it) : 
     it_{std::make_unique<InputIterator<T, It>>(it)}, 
     copy_{[this]{ return std::make_unique<InputIterator<T, It>>(static_cast<const InputIterator<T, It>&>(*this->it_)); }} 
    {} 
    TypeErasedIterator(const TypeErasedIterator& it) : it_{it.copy_()}, copy_{it.copy_} {} 
    T& operator*() { return **it_; } 
    TypeErasedIterator& operator++() { ++*it_; return *this; } 
    bool operator!=(const TypeErasedIterator& it) const { return *it_ != *it.it_; } 
}; 

// std::iterator_traits partial specialization for TypeErasedIterator's 
namespace std { 

template<typename T> 
struct iterator_traits<TypeErasedIterator<T>> { 
    using difference_type = std::ptrdiff_t; 
    using value_type = T; 
    using pointer = T*; 
    using reference = T&; 
    using iterator_category = std::input_iterator_tag; 
}; 

} 

На данный момент, ваша Belief иерархия может быть определена следующим образом:

template<class T> 
struct Belief { 
    virtual void SetFacts(TypeErasedIterator<T> beg, TypeErasedIterator<T> end, bool Append = false) = 0; 
}; 

template <class T> 
class BeliefSet : public Belief<T> 
{ 
private: 
    std::vector<T> m_Facts; 

public: 
    void SetFacts(TypeErasedIterator<T> beg, TypeErasedIterator<T> end, bool Append = false) override 
    { 
     if(!Append) 
     { 
      m_Facts.clear(); 
     } 
     m_Facts.insert(m_Facts.end(), beg, end); 
     std::cout << "m_Facts.size() = " << m_Facts.size() << '\n'; 
    } 
}; 

int main() 
{ 
    std::vector<int> v{0, 1, 2}; 
    std::list<int> l{3, 4}; 
    BeliefSet<int> bs; 
    bs.SetFacts(v.begin(), v.end()); 
    bs.SetFacts(l.begin(), l.end(), true); 
} 

Как вы можете видеть, SetFacts() принимает итераторы как от std::vector и std::list; кроме того, внутри реализации ваших производных классов вы не вынуждены обрабатывать один элемент последовательности за раз, но вы можете управлять всей последовательностью (например, вы можете изменить порядок последовательности или передать итераторы на любой стандарт алгоритм, поддерживающий их).

Обратите внимание, что моя реализация концепции InputIterator является неполной и минимальной, чтобы ваш пример работал.

+0

Определенно многословный, но мне это как-то нравится. +1 – skypjack

Смежные вопросы