【问题标题】:Fully specialized class as template function parameter完全专业化的类作为模板函数参数
【发布时间】:2014-05-22 09:32:02
【问题描述】:

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

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&lt;ContainerType1&gt;Container&lt;ContainerType2&gt; 之外的任何其他内容,我也会收到编译器错误(模板参数推导失败),但我更喜欢它,因为它提供了关于预期模板参数类型的提示.

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

【问题讨论】:

  • 这是可能的解决方案之一。您也可以使用std::enable_if 或显式模板实例化声明+定义。这两种解决方案都使用 C++11。

标签: c++ templates


【解决方案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&lt;T&gt;::value 上进行测试,或者在is_container&lt;T&gt;{} 上进行标签调度。

【讨论】:

    【解决方案2】:

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

    归根结底,您始终必须选择最佳方法。第二个可能看起来有点矫枉过正,尽管它明白了这一点。 如果您的 Container 类都有一个共同的功能(假设 Container1::hasPackage() or Container2::hasPackage() 并且您选择在 my_function 内调用它,那么它会立即表明您有资格调用它是该函数本身。在经历了许多这样的在项目中,您将以相反的方式开始阅读模板 - 从模板定义开始 - 看看需要哪些最少的属性来限定特定的类。

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

    One example I created on ideone 正在使用您的类,但向它们都添加了成员​​变量 name,这是 my_function 所期望的。当然,可能会有支持name的类,但开发者也可能为了实现函数背后的想法而费尽心思。

    【讨论】:

    • 嗨 AbhiP,我在 ideone 上查看了您的示例。由于my_function 使用容器的公共接口,它最终将无法与缺少该接口的所有其他类一起编译。从这个角度来看,增加一个额外的成员变量在我看来并没有任何好处,相当于原来的实现。编译器会通知我传递给my_function 的对象没有成员name。但真正的问题是:如何表达参数类型为Container 的要求?
    • 同意。这不是最大的好处,实际上是一个有争议的问题。正如我提到的,你的第一个版本很好。我的建议是帮助使编译器检查更严格。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-11
    • 1970-01-01
    • 2011-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-28
    相关资源
    最近更新 更多