【问题标题】:Why operator<< is ambiguous?为什么 operator<< 是模棱两可的?
【发布时间】:2017-03-22 16:10:37
【问题描述】:

我在全局范围内定义了一个函数模板为

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

然后

std::cout << "\n";

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

【问题讨论】:

  • 显示完整错误,大多数编译器会列出替代方案
  • 我相信,因为已经存在为此的标准重载。
  • ...标准版本也在输出流上进行了模板化。猜测您已经使用非模板化的一个参数进行了重载,而标准提供了使用非模板化的另一个参数的重载。
  • 试试template &lt;class T, class CharT, class Traits&gt;...std::basic_ostream&lt;CharT, Traits&gt;&amp; os...

标签: c++ overloading


【解决方案1】:

您在此处的operator&lt;&lt; 版本是基于一个参数的模板,标准提供的版本也是如此。

问题在于您的版本(我们将其称为函数 a)在一个参数上进行了模板化:

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

cppreference,您可以看到标准对const char* 的重载(我们称之为b)在另一个模板上:

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

您可以通过使您的operator&lt;&lt; 版本对第一个参数使用相同的模板参数(这个是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;
}

编译愉快,你可以看到on coliru

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

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

[temp.func.order]:

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

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

[temp.deduct.partial]:

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

  2. 用于确定排序的类型取决于完成部分排序的上下文:

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

...

  1. 函数模板 F 至少与函数模板 G 一样特化,如果对于用于确定排序的每一对类型,来自 F 的类型至少与来自 G 的类型一样特化。如果 F 比 G 更特化F 至少和 G 一样特化,而 G 至少不像 F 那样特化。

所以,我们希望模板的部分是函数参数。

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 不是这种情况,因为 ac 的所有参数至少与 c 中的参数一样专用em>c.

因此,在 ab 之间进行选择时,重载决策将是模棱两可的,因为 a 至少与 b 和 b 至少与 a 一样专业。通过使用 c 而不是 a 可以消除歧义,因为 b 至少与 c 一样特化,但是反向不正确。

从 [temp.func.order]/2 中我们知道,更专业的函数会赢得重载决议,并且使用 c 而不是 a,获胜者是 bstd::cout &lt;&lt; "hello"; 行将 hello 打印到控制台。

【讨论】:

  • "您可以通过使您的操作符版本"来获得您期望的行为 - 返回值不应该也是模板化以匹配参数? template&lt;class T, class CharT, class Traits&gt; inline std::basic_ostream&lt;CharT, Traits&gt;&amp; operator&lt;&lt;(std::basic_ostream&lt;CharT, Traits&gt;&amp; os, T const&amp; t)std::ostreamstd::basic_ostreamchar 的特化,但如果 CharT 不是 char 怎么办?
  • @RemyLebeau 是的,谢谢你的发现(尽管它实际上并没有改变这里的重载分辨率。有趣的时候。)
  • 我想就是这样。我可以列出定义如何确定参数更专业的完整规则集,但考虑到这些是相对简单的情况,而且这些情况很长(并且通过遵循标准的链接很容易发现),这有点过分了
【解决方案2】:

有一个函数模板

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

在标准库中声明。

替换后,调用标准的专业化和你的专业化所需的转换,a.k.a.

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

都被评为完全匹配

然后编译器尝试通过partial ordering rules解决这个调用:

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

【讨论】:

    猜你喜欢
    • 2018-06-01
    • 2018-05-28
    • 1970-01-01
    • 2013-08-17
    • 2012-12-08
    • 1970-01-01
    • 2020-02-23
    • 2012-07-14
    • 2021-11-28
    相关资源
    最近更新 更多