5

好的,这有点复杂,所以请多多包涵。:)

我们有这个简单的类层次结构:

class A {};
class DA : public A {};
class DDA : public DA {};

我们在这些类上运行了以下函数:

void f(A x) {
  std::cout << "f A" << std::endl;
}
void f(DA x) {
  std::cout << "f DA" << std::endl;
}
void f(DDA x) {
  std::cout << "f DDA" << std::endl;
}

现在我们想添加另一个处理 DA 的函数。

(1) 第一次尝试可能如下所示:

void g(A t) {
  std::cout << "generic treatment of A" << std::endl;
  std::cout << "called from g: ";
  f(t);
}
void g(DA t) {
  std::cout << "special treatment of DA" << std::endl;
  std::cout << "called from g: ";
  f(t);
}

但是用每个类的对象调用它显然不会产生预期的效果。

称呼:

  A a; DA b; DDA c;
  g(a); g(b); g(c)

结果:

generic treatment of A
called from g: f A
special treatment of DA
called from g: f DA
special treatment of DA
called from g: f DA        //PROBLEM: g forgot that this DA was actually a DDA

(2) 因此,我们可能会尝试使用模板:

template<typename T>
void h(T t) {
  std::cout << "generic treatment of A" << std::endl;
  std::cout << "called from h: ";
  f(t);
}

template<>
void h<>(DA t) {
  std::cout << "special treatment of DA" << std::endl;
  std::cout << "called from h: ";
  f(t);
}

这导致:

generic treatment of A
called from h: f A
special treatment of DA
called from h: f DA
generic treatment of A    //PROBLEM: template specialization is not used
called from h: f DDA

那么,我们不使用模板特化,而是为特殊情况定义一个非模板函数怎么样?(关于非常令人困惑的问题的文章。)事实证明,它的行为方式完全相同,因为根据文章“一等公民”的非模板函数似乎丢失了,因为使用它必须进行类型转换. 如果它会被使用,那么我们就会回到第一个解决方案(我假设)并且它会忘记 DDA 的类型。

(3) 现在我在工作中遇到了这段代码,这对我来说似乎很花哨:

template<typename T>
void i(T t, void* magic) {
  std::cout << "generic treatment of A" << std::endl;
  std::cout << "called from i: ";
  f(t);
}

template<typename T>
void i(T t, DA* magic) {
  std::cout << "special treatment of DA" << std::endl;
  std::cout << "called from i: ";
  f(t);
}

但它似乎正是我想要的:

generic treatment of A
called from i: f A
special treatment of DA
called from i: f DA
special treatment of DA
called from i: f DDA

即使它需要以一种奇怪的方式调用:i(a, &a); i(b, &b); i(c, &c);

现在我有几个问题:

  1. 为什么这会奏效?
  2. 你认为这是个好主意吗?可能的陷阱在哪里?
  3. 你会建议哪些其他方式来进行这种专业化?
  4. (类型转换如何适应模板部分排序等疯狂......)

我希望这是相当清楚的。:)

4

1 回答 1

4

函数模板重载

template<typename T>
void i(T t, DA* magic) {

仅当参数magic可转换为 type时才可用DA *。这显然是这种情况,&b但也&c因为指向派生的指针可转换为指向基址的指针。void *函数模板重载始终可用,但优先DA *void *§13.3.3.2:4:

13.3.3.2 对隐式转换序列进行排名[over.ics.rank]

[...]

4标准转换序列按其等级排序:完全匹配是比提升更好的转换,这是比转换更好的转换。除非以下规则之一适用,否则具有相同等级的两个转换序列是无法区分的:

[...]

— 如果类B直接或间接派生自类A,则B*toA*的转换优于to 的转换, B*to的void*转换优于A*to的转换。void*B*void*

正如您所指出的,这是一个完全可行的方案。i将魔法包装在另一个负责调用的模板函数中会更有意义(a, &a)

template<typename T>
void j(T t) {
    i(t, &t);
}

在安全方面,没问题;如果DA *重载丢失,那么void *重载将被静默选择;由您决定这是否可取。

作为替代方案,您可以使用std::enable_if在模板之间进行选择:

template<typename T>
typename std::enable_if<!std::is_base_of<DA, T>::value>::type g(T t) {
  std::cout << "generic treatment of A" << std::endl;
  f(t);
}
template<typename T>
typename std::enable_if<std::is_base_of<DA, T>::value>::type g(T t) {
  std::cout << "special treatment of DA" << std::endl;
  f(t);
}
于 2012-07-25T13:40:07.390 回答