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

时间:2014-05-22 09:32:03

标签: c++ templates

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

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 Concept Check Library的存在。 为了论证,让我们假设我不想通过使用继承和虚函数来解决问题。 如果它与讨论相关,则使用CRTP强制Container1Container2的公共接口。将来可能会有更多的容器类。

2 个答案:

答案 0 :(得分:1)

这种问题有一些解决方案。

您的解决方案(将您的类型实施为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());
}

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

另一个选项是标记特征类,用于标记您要支持的类型,例如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>{}上进行广告代码调度。

答案 1 :(得分:0)

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

在一天结束时,您总是必须选择最佳方法。第二个可能看起来像一个矫枉过正,虽然它得到了重点。 如果您的容器类都有一个共同的功能(让我们说Container1::hasPackage() or Container2::hasPackage()并且您选择在my_function内调用它,那么它会立即将您的观点指向它,即调用它的资格是在经历了许多这样的项目之后,你将开始以相反的方式阅读模板 - 从模板定义开始 - 看看需要什么样的属性来限定特定的类。

说完这一切之后,也许你的问题更适合Code Review

One example I created on ideone正在使用您的类,但向name添加成员变量my_function。当然可能会有类支持name,但开发人员也可能会烧几次手指以实现功能背后的想法。