将虚方法作为接收迭代器的模板

时间:2017-01-08 21:37:27

标签: c++ c++11 templates virtual-functions

我知道不能在C ++中使用模板用于虚拟方法(反之亦然),例如讨论herehere。不幸的是,我不知道如何在我的情况下处理这个限制。

我们有一个包含方法模板的类模板:

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 ++中是不可能的,就像此刻编写此代码一样。那么处理这种情况的典型方法还有什么其他方法呢?

3 个答案:

答案 0 :(得分: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上查看。

答案 1 :(得分: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);
    } );
}

我们完成了。

此处存在效率损失,但您可以继续丰富类型擦除接口以减少它。一直到擦除&#34;插入到矢量的末尾&#34;几乎没有效率损失。

我给它起了一个不同的名字,因为它避免了一些过载烦恼。

答案 2 :(得分:1)

另一个solution类型删除标准库的输入迭代器,用户定义为TypeErasedIterator。这样,您可以将任何输入迭代器包装到TypeErasedIterator对象和您的类&#39;接口应该只处理那种类型。

#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::vectorstd::list的迭代器;此外,在派生类的实现中,您一次被迫处理序列的一个元素,但您可以管理整个序列(例如,您可以重新排序序列,或将迭代器传递给任何支持它们的标准算法。)

请注意,我的InputIterator概念的实现是不完整的,并且很少使您的示例正常工作。