虚函数与泛型编程

Virtual Functions and Generic Programming

本文关键字:泛型编程 函数      更新时间:2023-10-16

假设我有一个模板类:

template <class T>
class Foo
{
public:
    explicit Foo(const T& value)
        : m_Value(value)
    {
    }
    bool Bar(const T& value)
    {
        return m_Value == value;
    }
private:
    T m_Value;
};

假设我有其他用户类型例如:

class A
{
};
那么这段代码是完全有效的,即使类A没有定义相等操作符:
int main(int argc, char* argv[])
{
    A a;
    Foo<A> foo(a);
    return 0;
}

但如果我做 Foo:: Bar () 虚拟:

    virtual bool Bar(const T& value)
    {
        return m_Value == value;
    }

代码不再编译:

错误C2676: binary '==': 'A'没有定义此操作符或转换为预定义操作符

可接受的类型我完全理解为什么这是一个问题。如果我错了,请纠正我,但我的理解是,因为函数是虚拟的,编译器必须编译函数(即使它从未被调用),以便它可以在Foo的v表中引用它。

我想知道是否有办法解决这个问题。我希望有一个模板类来处理可能只实现部分接口的泛型类型。只要不使用丢失的位,代码应该可以很好地编译。这与许多STD容器的工作原理类似,但它们不使用虚函数。

我该怎么做?是否有一个优雅的解决方案?

正如上文Kerrek SB所解释的,虚函数总是在模板实例化时实例化的。因此,当不使用虚拟方法时,没有办法让您的程序编译得很好,如果使用它并且您想要包装的类不提供自己的operator==,则无法编译。

但是,您可以使程序在运行时崩溃(使用assert/terminate)或抛出异常。

免责声明:我不认为这是一个好主意去做你正在尝试做的事情,因为它允许创建不支持他们声称提供的接口的类。使用以下内容,风险自负。

这里的方法是为你想要提供的每个方法使用自定义类型特征,即使包装的类本身没有实现它。在上面的例子中,这只是operator==,对应的代码看起来像这样:
namespace traits {
template <typename T>
using operator_eq_t = decltype(std::declval<T>() == std::declval<T>());
template <typename, typename = void>
struct has_operator_eq : std::false_type {};
// check that operator== is defined and returns the correct type `bool`.
template <typename T>
struct has_operator_eq<T, std::void_t<operator_eq_t<T>>> 
    : std::is_same<operator_eq_t<T>, bool> {}; 
} // namespace traits

如果你还不能访问c++1z,你可以创建自己的std::void_t版本,其他的都是有效的c++ 14:

template <typename...>
using void_t = void

准备好之后,您可以创建带有标记dispatch的包装器类模板:

template <typename T>
class Foo : public IFoo<T> {
public:
    explicit Foo(T const& value)
        : m_Value(value) {
    }
    bool Bar(T const& value) override {
        return BarImpl(value, traits::has_operator_eq<T>{});
    }
private:
    T m_Value;
    bool BarImpl(T const& value, std::false_type) {
        // some sensible default, in this case you might
        // consider just to return false
        assert(!"Called `Bar` on class that does not implement `operator==`.");
        throw std::logic_error("Called `Bar` on class that does not implement `operator==`.");
    }
    bool BarImpl(T const& value, std::true_type) {
        return value == m_Value;
    }
};