您在此处的operator<< 版本是基于一个参数的模板,标准提供的版本也是如此。
问题在于您的版本(我们将其称为函数 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<< 版本对第一个参数使用相同的模板参数(这个是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]:
部分排序通过依次转换每个模板(见下一段)并使用函数类型执行模板参数推导来选择两个函数模板中的哪一个比另一个更专业。推演过程确定模板中的一个是否比另一个更专业。如果是这样,更专业的模板是偏排序过程选择的模板。
要生成转换后的模板,对于每个类型、非类型或模板模板参数(包括其模板参数包),分别合成一个唯一的类型、值或类模板,并将其替换为每次出现的那个模板的函数类型中的参数。 [ 注意:在为非类型模板参数合成的值的类型中替换占位符的类型也是唯一的合成类型。 — 尾注 ] ...
[temp.deduct.partial]:
使用两组类型来确定偏序。对于涉及的每个模板,都有原始函数类型和转换后的函数类型。 [ 注意:转换类型的创建在 [temp.func.order] 中描述。 — 尾注 ] 推演过程使用转换后的类型作为参数模板,将另一个模板的原始类型作为参数模板。对于偏序比较中涉及的每种类型,此过程执行两次:一次使用转换后的模板 1 作为参数模板,模板 2 作为参数模板,再次使用转换后的模板 2 作为参数模板和模板 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&
对于这些函数的第一个参数,a 比 b 和 c 更专业。
对于这些函数的第二个参数,b 比 a 和 c 更专业。
正如我们在 [temp.deduct.partial]/10 中了解到的,参数推导要求函数 f1 的所有相关参数至少与函数 f2 的所有参数一样特化 对于函数 f1 至少与 f2 一样特化,a 在这里不能比 b,因为每个参数都有一个比另一个匹配参数更专业的参数。
c 不是这种情况,因为 a 和 c 的所有参数至少与 c 中的参数一样专用em>c.
因此,在 a 和 b 之间进行选择时,重载决策将是模棱两可的,因为 a 至少与 b 和 b 至少与 a 一样专业。通过使用 c 而不是 a 可以消除歧义,因为 b 至少与 c 一样特化,但是反向不正确。
从 [temp.func.order]/2 中我们知道,更专业的函数会赢得重载决议,并且使用 c 而不是 a,获胜者是 b,std::cout << "hello"; 行将 hello 打印到控制台。