虚函数和泛型编程
Virtual Functions and Generic Programming
假设我有一个这样的模板class:
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
{
};
那么这段代码是完全有效的,即使 class 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;
}
代码不再编译:
error C2676: binary '==': 'A' 未定义此运算符或将类型 acceptable 转换为预定义运算符
我完全理解为什么这是个问题。如果我错了请纠正我,但我的理解是因为该函数是虚拟的,编译器必须编译该函数(即使它从未被调用过)以便它可以在 [ 的 v-table 中引用它=33=]Foo.
我想知道是否有办法解决这个问题。我想要一个模板 class 来处理可能只实现部分接口的泛型类型。只要不使用缺失的位,代码应该可以正常编译。这与许多 STD 容器的工作方式类似,但它们不使用虚函数。
我该怎么做?有没有优雅的解决方案?
正如上面 Kerrek SB 所解释的,虚函数总是在实例化模板时实例化。因此,如果不使用虚方法 并且 无法编译,以防万一它被使用并且 class 你想要wrap 不提供自己的 operator==
.
但是你可以让程序在运行时崩溃(assert
/terminate
)或者抛出异常。
免责声明:我不认为做你想做的事是个好主意,因为它允许创建 classes 而不是不支持他们声称提供的界面。使用以下内容需要您自担风险。
这里的方法是为您要提供的每个方法使用自定义类型特征,即使包装的 class 本身没有实现它。在你上面的例子中,这只是 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
有了它,您可以创建带有标签调度的包装器 class 模板:
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;
}
};
可以找到一个工作示例 here。
假设我有一个这样的模板class:
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
{
};
那么这段代码是完全有效的,即使 class 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;
}
代码不再编译:
error C2676: binary '==': 'A' 未定义此运算符或将类型 acceptable 转换为预定义运算符
我完全理解为什么这是个问题。如果我错了请纠正我,但我的理解是因为该函数是虚拟的,编译器必须编译该函数(即使它从未被调用过)以便它可以在 [ 的 v-table 中引用它=33=]Foo.
我想知道是否有办法解决这个问题。我想要一个模板 class 来处理可能只实现部分接口的泛型类型。只要不使用缺失的位,代码应该可以正常编译。这与许多 STD 容器的工作方式类似,但它们不使用虚函数。
我该怎么做?有没有优雅的解决方案?
正如上面 Kerrek SB 所解释的,虚函数总是在实例化模板时实例化。因此,如果不使用虚方法 并且 无法编译,以防万一它被使用并且 class 你想要wrap 不提供自己的 operator==
.
但是你可以让程序在运行时崩溃(assert
/terminate
)或者抛出异常。
免责声明:我不认为做你想做的事是个好主意,因为它允许创建 classes 而不是不支持他们声称提供的界面。使用以下内容需要您自担风险。
这里的方法是为您要提供的每个方法使用自定义类型特征,即使包装的 class 本身没有实现它。在你上面的例子中,这只是 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
有了它,您可以创建带有标签调度的包装器 class 模板:
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;
}
};
可以找到一个工作示例 here。