我知道不能在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 ++中是不可能的,就像此刻编写此代码一样。那么处理这种情况的典型方法还有什么其他方法呢?
答案 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::vector
和std::list
的迭代器;此外,在派生类的实现中,您不一次被迫处理序列的一个元素,但您可以管理整个序列(例如,您可以重新排序序列,或将迭代器传递给任何支持它们的标准算法。)
请注意,我的InputIterator
概念的实现是不完整的,并且很少使您的示例正常工作。