考虑以下代码:
#include <vector>
#include <iostream>
struct MySqlStream {
template<class T>
MySqlStream &operator<<( const std::vector<T> &values ) {
std::cout << "vector";
return *this;
}
};
template<class T>
MySqlStream &operator<<( MySqlStream &m, const T &x ) {
std::cout << "template";
return m;
}
int main() {
std::vector<double> temp;
MySqlStream sql;
sql << temp;
}
g++ 和 clang 都接受该代码(并使用该vector
版本)。
另一方面,MSVC 拒绝该代码,并指出:
<source>(23): error C2593: 'operator <<' is ambiguous
<source>(6): note: could be 'MySqlStream &MySqlStream::operator <<<double>(const std::vector<double,std::allocator<double>> &)'
<source>(13): note: or 'MySqlStream &operator <<<std::vector<double,std::allocator<double>>>(MySqlStream &,const T &)'
with
[
T=std::vector<double,std::allocator<double>>
]
<source>(23): note: while trying to match the argument list '(MySqlStream, std::vector<double,std::allocator<double>>)'
MSVC 拒绝这个是否不正确?我该如何解决这个问题? Godbolt Link
注意:我可以使用sql.operator<<( temp );
,但当有很多东西被连接在一起时,这并不好。
g++ 和 clang++ 是正确的,而 MSVC 是错误的:应该选择成员,
operator<<
因为它是更专业的模板。过载解析的主要步骤是:
构建一组候选函数。对于运算符表达式 ( [over.match.oper] ),这可以包括:
template<class T> MySqlStream & MySqlStream::operator<<(const std::vector<T> &);
template<class T> MySqlStream & ::operator<<(MySqlStream &, const T &);
,以及一些std::operator<<
函数模板对于集合中的每个非静态成员函数 [template],将其视为具有额外的第一个参数,其类型是对类类型的引用 ( [over.match.funcs.general]/2 )。相应的参数基于
.
或左侧的表达式->
,这可能涉及隐式this->
。在此示例中,template<class T> MySqlStream & MySqlStream::operator<<(MySqlStream &, const std::vector<T> &);
对于集合中的每个函数模板,进行模板参数推导以获得具体的函数类型([over.match.funcs.general]/8,[temp.deduct])。所有 的推导均失败
std::operator<<
,因此它们被抛出集合。我们得到:MySqlStream & operator<<(MySqlStream &, const std::vector<double> &);
MySqlStream & operator<<(MySqlStream &, const std::vector<double> &);
检查每个函数是否“可行”([over.match.viable])。粗略地说,这意味着根据参数类型、参数类型和约束来调用函数是有效的,但忽略一些其他规则,如私有或受保护的访问和已删除的函数。两个
MySqlStream
函数都是可行的。内置候选函数不可行,并从集合中丢弃。此时,MySqlStream
只剩下这两个函数。确定最佳可行函数 ( [over.match.best] )。这是本例中的关键部分。
由于此时函数类型相同,因此有关隐式转换序列的大部分文本都无法确定更好的函数。重要的规则是[over.match.best.general]/(2.5):
同样,非静态成员函数模板被认为具有额外的第一个参数,它是对类类型的引用([temp.func.order]/3)。虽然此规则的确切规则与 [over.match.funcs.general]/2 中的先前步骤不同,但在这种情况下,我们再次得到相同的结果
template<class T> MySqlStream & MySqlStream::operator<<(MySqlStream &, const std::vector<T> &);
[temp.func.order] 或相关的[temp.deduct.partial]中没有其他内容提及非静态成员函数的特殊第一个参数或第一个参数,因此唯一剩下的重要区别是函数参数类型
const std::vector<T> &
与const T &
。为了简化一点,现在让我们看一个处理这些问题的相关示例:[temp.deduct.partial] 的要点是为一个模板 F1 发明尽可能通用的模板参数,并查看与结果函数特化完全匹配的调用是否也与另一个原始模板 F2 匹配。如果匹配,则 F1“至少与 F2 一样特化”,这意味着(几乎)任何时候 F1 可行,F2 也同样可行。以两种方式执行此操作,如果只有一种方式成功,则该测试中的 F1 函数更特化。
因此,首先让我们专门化一下:发明一种与任何其他声明无关的
g1
类型,并将其替换为:T1
T
接下来看看是否
g2
可以使用完全匹配的参数进行调用。由于此处的参数是左值引用,因此参数应该是左值表达式。当然,
g2
推断它是可行的。T
至少和一样专业。std::vector<T1>
g1
g2
反过来,我们发明一种类型
struct T2 {};
并将其代入g2
:并且可以
g1
使用完全匹配的参数来调用吗?不,该
const T2
参数不是 的任何特化std::vector
,因此 的模板参数推导g1
失败。g2
至少不像 那样专业化g1
。 合起来,这些意味着g1
比 更专业化g2
。类似地,在原始示例中,“vector”成员函数模板 1 是更专业的函数模板和更好的可行函数,并且由表达式的重载解析选择
<<
。重载解析并不像 MSVC 声称的那样含糊不清。看起来 gcc 和 clang 是正确的。以下是来自 cppreference 的引用:
函数模板 - cppreference.com
在您的代码成员函数中,第二个参数比自由函数版本
const std::vector<T> &
更加专业。const T &
以下是一些实验: