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

Posted

技术标签:

【中文标题】这种使用模板对类层次结构进行函数重载的方式安全吗?【英文标题】:Is this way of function overloading for class hierarchies using templates safe? 【发布时间】:2012-07-23 22:50:35 【问题描述】:

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

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

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

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

现在我有几个问题:

    这到底是为什么? 您认为这是个好主意吗?可能的陷阱在哪里? 您还建议通过哪些其他方式进行此类专业化? (类型转换如何适应模板部分排序等疯狂......)

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

【问题讨论】:

看了你一半的帖子,想问一下:多态性呢? 呵呵,好问题。可能是 3. 的最佳答案。但由于复杂的遗留原因,我遇到这个问题不是一个选择。 :)(编辑:实际上最好的方法可能是访问者,因为其目的是分离出一些功能。) 我会尝试class A public: virtual void f(); ;,然后根据需要在派生类中提供重载。 【参考方案1】:

函数模板重载

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

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

c++11

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

[...]

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

[...]

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

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

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);

【讨论】:

"void * 函数模板重载始终可用,但 DA * 优于 void *。"这些是函数重载的正常规则吗?我在哪里可以了解此偏好? @CorporalTouchy 确实是;我已经提供了对上述标准的参考。要阅读标准(或其草案),请参阅***.com/questions/7747069/…

以上是关于这种使用模板对类层次结构进行函数重载的方式安全吗?的主要内容,如果未能解决你的问题,请参考以下文章

对类方法使用胖箭头语法时,我可以使用 TypeScript 重载吗?

C++中函数模板和模板函数的区别

多态与重载

26.C++- 泛型编程之类模板(详解)

C++ 函数重载,函数模板和函数模板重载,选择哪一个?

函数重载与函数模板 - C++