函数模板重载

Posted

技术标签:

【中文标题】函数模板重载【英文标题】:function template overloading 【发布时间】:2011-01-11 13:42:18 【问题描述】:

谁能总结一下的想法?什么重要,模板参数或函数参数?返回值呢?

例如,给定一个函数模板

template<typename X, typename Y> void func(X x, Y y) 

什么是重载的函数模板?

1) template<typename X> void func(X x, int y) 
2) template<typename X, typename Y> X func(X x, Y y) 
3) template<class X, class Y, class Z> void func(X x, Y y, Z z) 

【问题讨论】:

【参考方案1】:

在该列表中,只有第二个引入了歧义,因为函数 - 无论它们是否是模板 - 都不能根据返回类型重载。

你可以使用其他两个:

template<typename X> void func(X x, int y);

如果调用的第二个参数是 int 则使用,例如 func("string", 10);

template<class X, class Y, class Z> void func(X x, Y y, Z z);

如果你用三个参数调用 func 将被使用。


我不明白为什么其他一些答案提到模板函数和函数重载不混合。他们当然会这样做,并且有特殊规则如何选择要调用的函数。

14.5.5

函数模板可以是 与其他功能重载 模板和正常 (非模板)函数。一个正常的 功能与 a 无关 函数模板(即,它永远不会 被认为是专业化), 即使它具有相同的名称和类型 作为潜在生成的函数 模板专业化。)

非模板化(或“较少模板化”)重载优先于模板,例如

template <class T> void foo(T);
void foo(int);

foo(10); //calls void foo(int)
foo(10u); //calls void foo(T) with T = unsigned

您的第一个带有一个非模板参数的重载也属于此规则。

在多个模板之间进行选择时,首选更专业的匹配:

template <class T> void foo(T);
template <class T> void foo(T*);

int i;
int* p;
int arr[10];

foo(i);  //calls first
foo(p);   //calls second
foo(arr); //calls second: array decays to pointer

您可以在标准的同一章节中找到所有规则的更正式的描述(函数模板


最后,在某些情况下,两个或多个重载会产生歧义:

template <class T> void foo(T, int);
template <class T> void foo(int, T);

foo(1, 2);

这里的调用是模棱两可的,因为两个候选人都同样专业。

您可以使用(例如)boost::disable_if 来消除此类情况的歧义。例如,我们可以指定当 T = int 时,不应将第二个重载作为重载候选:

#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_same.hpp>
template <class T>
void foo(T x, int i);

template <class T>
typename boost::disable_if<boost::is_same<int, T> >::type
foo(int i, T x);

foo(1, 2); //calls the first

这里库在第二个重载的返回类型中产生“替换失败”,如果 T = int,则将其从重载候选集中删除。

在实践中,您应该很少遇到这样的情况。

【讨论】:

请注意,对于template &lt;class T&gt; void func(T); template &lt;class T&gt; void func(T*);,也可以制作一个专门处理数组的版本:template &lt;class T, size_t N&gt; void func((T&amp;)[N]); 这可以防止数组衰减为指针并调用指针版本。 我正在尝试使用template&lt;typename T&gt; class_name::class_name(const T&amp;);template&lt;typename T&gt; class_name::class_name(const T*); 来实例化一个类对象,但是当我传递一个字符数组时,它会尝试调用class_name(const T&amp;) 而不是class_name(const T*)。我收到一条错误消息 include/Class_name.h|67|error: no matching function for call to ‘to_string(const char [43])’|因为,在class_name(const T&amp;) 我使用to_string(T) 而在class_name(const T*) 我专门处理char 数组。 @GaurishGangwar 你应该发布你自己的问题。但是,是的,通过引用按原样获取数组的重载比让它衰减到指针更好。在 Chris 给出的情况下,它衰减为指针的原因是因为他提出了一种按值传递的方案,这对于原始 C 样式数组是不可能的,因此将强制衰减为指针。【参考方案2】:

这里有两个不同的东西:函数模板和函数重载。任何两个不同的模板声明都可能是彼此的重载,所以你的问题并不像所说的那样有意义。 (您给出的三个“重载”不是基于第一个模板,而是对同一个函数名有四个重载。)真正的问题是,给定一些重载和一个调用,如何调用想要的过载?

首先,无论是否涉及模板,返回类型都不参与重载过程。所以 #2 永远不会和 #1 配合得很好。

其次,函数模板重载解析的规则与更常用的类模板特化规则不同。两者本质上都解决了同一个问题,但是

类模板的规则更简单、更强大,例如允许递归和(成员)函数仅在返回类型上有所不同 函数模板的规则允许编译器根据函数参数类型计算模板参数

您也许可以通过函数模板重载来解决您的特定问题,但您可能无法修复由于规则较长且熟悉其复杂性的人较少而出现的任何错误。经过几年的模板黑客攻击后,我没有意识到微妙的函数模板重载甚至是可能的。在 Boost 和 GCC 的 STL 等库中,一种替代方法无处不在。使用模板化包装类:

template< typename X, typename Y >
struct functor 
    void operator()( X x, Y y );
;
template< typename X > // partial specialization: 
struct functor< X, int >  // alternative to overloading for classes
    void operator()( X x, int y );
;

现在你牺牲了隐式实例化语法(没有尖括号)。如果你想找回它,你需要另一个函数

template< typename X, typename Y > void func( X x, Y y ) 
    return functor< X, Y >()( x, y );

我很想知道函数重载是否可以做任何类[部分]专业化不能做的事情(除了演绎)......

当然,您的重载 #3 永远不会有歧义,因为它的参数数量与任何其他重载不同。

【讨论】:

你的例子似乎并不模棱两可。该调用与第二个匹配(一个参数与 int 完全匹配)。也许您的意思是重载,例如:template&lt;typename X&gt; void func(int x, X y); template&lt;typename X&gt; void func(X x, int y); 第二个func 将拒绝第一个func 接受的一些参数,但反过来不会,这使得模板更加专业。对于一个普通的函数调用,如果我们忽略这个顺序,调用就会有歧义,因为模板参数同样被推导出为int @UncleBens:哇,谢谢,我完全不知道函数模板部分排序方案等等。我在现实生活中从未偶然发现它。很高兴知道!【参考方案3】:

除了 cmets,还有一些关于主题的更多信息在 Herb Sutters 的文章Why Not Specialize Function Templates。希望它也会有所帮助。

【讨论】:

【参考方案4】:

我的立场是正确的 - 请参阅下面的 cmets。我不会更改任何原始帖子,因为这会删除回复的上下文。我感谢评论者的意见,并感谢他们没有投票给我


将模板视为宏预处理器,它会在编译器看到它们之前扩展#defines。

编译器将“扩展”您的模板参数,然后查看您的函数声明。所以,模板参数==函数参数。如果你两次声明同一个函数,你会得到一个错误。

您询问返回类型。这是函数“签名”的一部分。具有相同参数但返回类型不同的两个函数是两个不同的函数。

【讨论】:

非模板函数的返回类型不是其签名的一部分(参见***.com/questions/290038/…)。 与预处理器宏的比较是相当有缺陷的。有关于重载的特殊规则。如果你有template &lt;class T&gt; void foo(T);void foo(int);,那么像foo(3); 这样的调用肯定会解析为后者(如果它们完全匹配,非模板优先于模板)。 如果返回类型是签名的一部分,您将能够重载重新运行类型,但您不能 需要注意的是,签名并不是主要用于确定某物是否可以重载。 “签名”定义中的句子“有关参与重载解析的函数的信息”已从 c++0x 中删除,因为它具有误导性。重载是在声明上完成的,而不是在函数本身上:namespace A void f(); void f(); using A::f; 即使它们有不同的签名,它们的两个声明也不能重载。另请参阅open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#357【参考方案5】:
void fun(int i)
    cout<<i<<endl;


void fun(int i, string str)
    cout<<i<<str<<endl;


template <typename ... Args>
void sender(Args&& ... args)

    fun(forward<Args>(args) ...);


int main()
    sender(5, "hello");
    sender(7);

【讨论】:

以上是关于函数模板重载的主要内容,如果未能解决你的问题,请参考以下文章

为啥具有“相同签名”的模板和非模板函数重载调用非模板函数?

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

函数模板重载

函数模板遇上函数重载

模板函数 重载/特化

数据的间距问题(重载+函数模板)