这种使用模板安全的类层次结构的函数重载方式吗?

Sar*_*ien 5 c++ templates overloading class

好的,这有点复杂所以请耐心等待.:)

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

class A {};
class DA : public A {};
class DDA : public DA {};
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

现在我们想要添加另一个以不同方式处理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);
}
Run Code Online (Sandbox Code Playgroud)

但是,用每个类的对象调用它显然没有达到预期的效果.

呼叫:

  A a; DA b; DDA c;
  g(a); g(b); g(c)
Run Code Online (Sandbox Code Playgroud)

结果:

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
Run Code Online (Sandbox Code Playgroud)

(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);
}
Run Code Online (Sandbox Code Playgroud)

这导致:

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
Run Code Online (Sandbox Code Playgroud)

那么,我们不使用模板专业化但是为特殊情况定义非模板函数呢?(文章在非常混乱的问题.)事实证明,它的行为完全相同的方式,因为这是根据该报道的"一等公民"的非模板函数,似乎失去了,因为类型转换需要使用它.如果它会被使用,那么我们就会回到第一个解决方案(我假设),它会忘记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);
}
Run Code Online (Sandbox Code Playgroud)

但它似乎完全符合我的要求:

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
Run Code Online (Sandbox Code Playgroud)

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

现在我有几个问题:

  1. 为什么这会起作用?
  2. 你认为这是个好主意吗?哪些可能存在陷阱?
  3. 您会建议使用哪种其他方式进行此类专业化?
  4. (类型转换如何适应模板偏序等的疯狂......)

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

eca*_*mur 4

函数模板重载

\n\n
template<typename T>\nvoid i(T t, DA* magic) {\n
Run Code Online (Sandbox Code Playgroud)\n\n

magic仅当参数可转换为 type时才可用DA *。这显然是这种情况,&b但也是因为&c派生指针可以转换为基指针。函数void *模板重载始终可用,但DA *优先于void *,根据 \xc2\xa713.3.3.2:4:

\n\n
\n

\n\n

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

\n\n

[...]

\n\n

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

\n\n

[...]

\n\n

\xe2\x80\x94 如果类B直接或间接从类派生,则toA的转换优于to的转换\n ,并且to的转换优于to的转换。B*A*B*void*A*void*B*void*

\n
\n\n

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

\n\n
template<typename T>\nvoid j(T t) {\n    i(t, &t);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

从安全性来说,还好;如果DA *过载丢失,则将void *静默选择该过载;这是否值得由您决定。

\n\n

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

\n\n
template<typename T>\ntypename std::enable_if<!std::is_base_of<DA, T>::value>::type g(T t) {\n  std::cout << "generic treatment of A" << std::endl;\n  f(t);\n}\ntemplate<typename T>\ntypename std::enable_if<std::is_base_of<DA, T>::value>::type g(T t) {\n  std::cout << "special treatment of DA" << std::endl;\n  f(t);\n}\n
Run Code Online (Sandbox Code Playgroud)\n