为什么参数匹配适用于非模板版本,但不适用于模板版本

Why argument matching works for non template version, but does not work for template version?

本文关键字:版本 适用于 不适用 参数 为什么      更新时间:2023-10-16

考虑这个非常简单的例子,其中我有一个模板包装类,为其定义了运算符:

template <class T>
struct W {
W(const T&) {}
};
template <class T> 
T operator + (const W<T>& w1, const W<T>& w2) { return T(); }

令人惊讶的是,这与包装类型不匹配,gcc4.5.1中的一个简单错误无法找到operator + (A,A):

struct A {};
int main() {
A a1, a2;
A a3 = a1 + a2;
}

我试图通过测试非模板包装器来找到原因,但这个版本有效:

struct A {};
struct WA {
WA(const A&) {}
}; 
A operator + (const WA& w1, const WA& w2) { return A(); }
int main() {
A a1, a2;
A a3 = a1 + a2;
}

出现这种参数匹配差异的原因是什么?如何使模板版本有效?

[更新]

我考虑了你的答案,我把这个例子改成了相反的方式,仍然得到了相同的结果——模板版本抱怨缺少运算符+,而非模板版本运行良好。现在我有了显式强制转换操作符到显式包装器类:

模板版本

template <class T>
struct W {
};
template <class T> 
T operator + (const W<T>& w1, const W<T>& w2) { return T(); }
struct A {
operator W<A>() const { return W<A>(); }
};

不是模板

struct WA {
}; 
struct A {
operator WA() const { return WA(); }
};
A operator + (const WA& w1, const WA& w2) { return A(); }

我正试图避免这种解决方案:

A a3 = W(a1) + W(a2);

没有希望这样做吗?

A a3 = a1 + a2;

模板参数推导出现在重载解析之前。但是模板参数推导不考虑隐式转换,所以您的模板化操作符甚至从未被考虑过。

只有在选择了重载之后,才会应用隐式转换。

不过,你可以说A a3 = W(a1) + W(a2);

您的第一个例子失败了,因为标准规定它必须在使用模板时失败。依赖于参数的名称查找可能存在问题,而模板会加剧这些问题。解决此问题的一种方法是将模板排除在此类查找之外,除非您明确使用模板。以下是C++03标准的相关文本,第14.8.1节第6段:

对于简单函数名,即使函数名在调用范围内不可见,也会应用参数相关查找(3.4.2)。这是因为调用仍然具有函数调用的语法形式(3.4.1。如果没有这样的名称可见,则该调用的语法格式不正确,并且不应用依赖于参数的查找。如果某个这样的名称可见,则应用依赖于参数的查找,并且可能在其他命名空间中找到其他函数模板。

更新:
已使用其他信息对问题进行了编辑。

我正试图避免此解决方案:
A a3 = W(a1) + W(a2);

你为什么要避免这种情况?您的这段代码违反了编程的两个关键规则。

  1. 密码,就好像下一个维护你密码的人是一个知道你住在哪里的杀人狂
  2. 最小惊奇原则

即使您的非模板版本也会违反这些规则。通过对不相关类W的(隐藏)转换来计算a1+a2,其中operator+返回A而不是W。这不是最不惊奇的原则。至少可以说,这是令人惊讶的你为什么不认为显式转换是更好的方法呢

依赖于参数的查找是一个非常强大的工具,但与这种功能相关的问题很多。将ADL与自动类型转换结合起来,问题就会扩大。再加上自动类型转换和模板,你就会一团糟。标准委员会决定适可而止。除非明确转换,否则无法执行您想要执行的操作。