作为模板函数参数的完全特化类

Fully specialized class as template function parameter

本文关键字:参数 函数      更新时间:2023-10-16

我编写了两个不同的容器类,它们具有相同的接口,但使用不同的成员数据和算法来操作其成员。我也有一个模板函数,它接受一个容器并做一些有用的计算:

class Container1
{
  // implementation here
};
class Container2
{
  // implementation here
};
template<typename ContainerType>
void my_function(ContainerType const& container, /* other parameters */)
{
 // ...
}

困扰我的是'my_function'应该只接受Container1Container2,但这不是由代码表示的,因为ContainerType可以是任何类型。该函数是按容器类型模板化的,因为无论容器的内部实现是什么,它都做同样的事情。我正在考虑一种变体,其中Container1Container2将是模板类的完全专门化。然后我可以更具体地说明my_function的参数:

template<typename T>
class Container;
// Tags to mark different container types
struct ContainerType1 { };
struct ContainerType2 { };
template<>
class Container<ContainerType1>
{
  // implementation
};
template<>
class Container<ContainerType2>
{
  // implementation
};
template<typename T>
void my_function(Container<T> const& container, /* other parameters */)
{
}

在第一种情况下,如果'ContainerType'没有my_function所需的接口,那么使用错误模板参数的编译将失败,这不是很有用。在第二种情况下,如果我提供Container<ContainerType1>Container<ContainerType2>以外的任何东西,我也会得到编译器错误(失败的模板参数推导),但我更喜欢它,因为它提供了关于期望哪种模板参数的提示。

你对此有什么看法?这是否是一个好的设计理念?您认为值得对代码进行更改吗?代码中还有许多其他函数,如my_function,有时它们期望的模板参数类型并不明显。我有什么其他的选择使my_function更具体?我知道Boost概念检查库的存在。为了便于讨论,让我们假设我不想通过使用继承和虚函数来解决问题。如果与讨论相关,Container1Container2的公共接口是通过使用CRTP强加的。将来可能会有更多的容器类

对于这类问题有几种解决方法。

你的解决方案(实现你的类型作为template专门化)是一个,但我不是特别喜欢。

另一个是CRTP:
template<typename T>
struct Container {
  // optional, but I find it helpeful
  T* self() { return static_cast<T*>(this); }
  T const* self() const { return static_cast<T const*>(this); }
  // common code between every implementation goes here.  It accesses itself through self(), never this
};
class ContainerType1: public Container<ContainerType1> {
  // more details
};
class ContainerType2: public Container<ContainerType2> {
  // more details
};

这是CRTP的核心。

:

template<typename T>
void my_function(Container<T> const& container_, /* other parameters */)
{
  T const& container = *(container.self());
}

Bob是你的叔叔。作为奖励,这提供了放置公共代码的地方。

另一个选项是标记特征类,标记你想要支持的类型,如iterator_traits

template<typename T>
struct is_container : std::false_type {};
template<>
struct is_container<ContainerType1> : std::true_type {};
template<>
struct is_container<ContainerType2> : std::true_type {};

你甚至可以做SFINAE风格的模式匹配来检测基类型(就像迭代器的工作方式一样)。

现在你的方法可以在is_container<T>::value上测试,或者在is_container<T>{}上做标签调度。

我认为你的第一个版本是可行的。

在一天结束的时候,你总是要选择最优的方法。第二个可能看起来有点矫枉过正,尽管它表达了重点。如果您的容器类都有一个共同的函数(假设是Container1::hasPackage() or Container2::hasPackage()),并且您选择在my_function中调用它,那么它会直接表明您的观点,即调用它的资格是该函数本身。在经历了许多这样的项目之后,您将开始以相反的方式阅读模板——从模板定义开始——以查看限定一个特定类所需的最少属性。

说了这么多,也许你的问题更适合Code Review

我在ideone上创建的一个例子是使用您的类,但将name成员变量添加到它们中,这是my_function所期望的。当然,可能会有支持name的类,但开发人员也可能会花几次时间来实现函数背后的思想。