为什么运算符<<是模棱两可的?



我将全局范围内的函数模板定义为

template<class T>
inline std::ostream& operator<<(std::ostream& os, T const& t)
{       
return os;
}

然后

std::cout << "n";

由于歧义而无法编译。我认为这是函数重载,编译可以解决它,并为char const*选择更具体的版本。为什么不编译?

您在此处operator<<的版本是在一个参数上模板化的,标准提供的版本也是如此。

问题在于您的版本(我们称之为函数a)是在一个参数上模板化的:

template<class T>
inline std::ostream& operator<<(std::ostream& os, T const& t)
{       
return os;
}

从 CPP 首选项中,您可以看到标准对const char*的重载(我们称之为B)在另一个模板化:

template< class Traits >
basic_ostream<char,Traits>& operator<<( basic_ostream<char,Traits>& os,  
const char* s );

您可以通过使您的版本operator<<对第一个参数使用相同的模板参数(这个是c)来获得您期望的行为:

template<class T, class CharT, class Traits>
inline std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, T const& t)
{       
return os;
}

编译愉快,正如您在 Coliru 上看到的那样。

这是因为在选择具有多个候选模板函数的重载时,模板重载选择会选择最专业的模板。

具体来说,首先转换模板:

[临时函数顺序]:

  1. 部分排序通过依次转换每个模板(请参阅下一段)并使用函数类型执行模板参数推导来选择两个函数模板中哪个比另一个更专业。演绎过程确定其中一个模板是否比另一个模板更专业。如果是这样,则更专业的模板是部分排序过程选择的模板。

  2. 要生成转换后的模板,请针对每个类型、非类型或模板模板参数(包括其模板参数包)分别合成唯一的类型、值或类模板,并将其替换为模板函数类型中每次出现的该参数。[ 注意:为非类型模板参数合成的值的类型中替换占位符的类型也是唯一的合成类型。 — 尾注 ]...

[温度扣除部分]:

  1. 组类型用于确定部分排序。对于涉及的每个模板,都有原始函数类型和转换后的函数类型。[注意:转换类型的创建在 [temp.func.order] 中描述。 — 尾注 ]演绎过程使用转换后的类型作为参数模板,使用另一个模板的原始类型作为参数模板。对于偏序比较中涉及的每种类型,此过程执行两次:一次

    使用转换后的模板 1 作为参数模板,模板 2 作为参数模板,再次使用转换后的模板 2 作为参数模板,模板 1 作为参数模板。
  2. 用于确定排序的类型取决于执行部分排序的上下文:

    在函数调用
    1. 的上下文中,使用的类型是函数调用具有参数的那些函数参数类型。

    函数模板 F
  1. 至少与函数模板 G 一样专用,如果对于用于确定排序的每对类型,F 中的类型至少与 G 中的类型一样专用。

因此,我们想要模板中作为函数参数的部分。

a) T                 =>  std::ostream&,                     T const&
b) CharT, Traits     =>  std::basic_ostream<CharT, Traits>, const char*
c) T, CharT, Traits  =>  std::basic_ostream<CharT, Traits>, T const&

对于这些函数的第一个参数,abc更专业。

对于这些函数的第二个参数,bac更专业。

正如我们在 [temp.deduct.partial]/10 中学到的,参数推导要求函数 f1 的所有相关参数至少与函数 f2 的所有参数一样专业,函数f1至少与f2一样专业,这里的 a不能比b更专业,因为每个参数都有一个参数比另一个的匹配参数更专业。

c不是这种情况,因为a和 c 的所有参数至少与c中的参数一样专业。

因此,在a和b之间进行选择时,过载分辨率将是模棱两可的,因为a至少与b一样专业b至少与a一样专业。通过使用 c 而不是a来消除歧义,因为b至少与c一样专业,但反之则不然。

正如我们从 [temp.func.order]/2 中知道的,更专业的函数赢得重载分辨率,并且使用c而不是a,获胜者是b,行std::cout << "hello";打印hello到控制台。

有一个函数模板

template <class Traits>
... operator<<(basic_ostream<char, Traits> &, const char *); // @1

在标准库中声明。

替换后,转换需要调用标准的专业化和你的,又名

template<class T>
... operator<<(std::ostream& os, T const& t); // @2

两者都被列为完全匹配

然后编译器尝试通过部分排序规则来解决此调用:

@1 from transformed @2 :
(basic_ostream<char, T> &, const char *) from (std::ostream& os, U const& t)
: P2 = const char *, A2 = U const & : deduction fails
@2 from transformed @1 :
(std::ostream& os, T const& t) from (basic_ostream<char, U> &, const char *)
: P1 = std::ostream&, A1 = basic_ostream<char, U> & : deduction fails

因此,两者都不是更专业的,呼吁是模棱两可的。


还有其他一些候选人:

operator<<(basic_ostream<_CharT, _Traits>&, const char*)
operator<<(basic_ostream<_CharT, _Traits>&, const _CharT*)

但可悲的是,在这种情况下,他们都不如老大哥@1那么专业。所以我把他们赶了出去...

还有一些,但这两个只是为了使错误消息的可读性降低。

operator<<(bool __n)
operator<<(const void* __p)

相关内容

最新更新