重载转换运算符模板

22
考虑以下简单示例。
struct C
{
    template <typename T> operator T () {return 0.5;}
    operator int () {return 1;}
    operator bool () {return false;}
};

int main ()
{
    C c;
    double x = c;
    std::cout << x << std::endl;
}

当使用Clang编译时,会出现以下错误。
test.cpp:11:12: error: conversion from 'C' to 'double' is ambiguous
    double x = c;
           ^   ~
test.cpp:4:5: note: candidate function
    operator int () {return 1;}
    ^
test.cpp:5:5: note: candidate function
    operator bool () {return false;}
    ^
test.cpp:3:27: note: candidate function [with T = double]
    template <typename T> operator T () {return 0.5;}
                          ^
1 error generated.

其他编译器也会生成类似的错误,例如GCC和Intel iclc。

如果我删除 operator intoperator bool,它就可以编译并按预期工作。如果只删除其中一个,即保留模板运算符并保留 operator int,那么非模板版本将始终被选择。

我的理解是,只有当模板和非模板重载函数相等时,即它们都是完美匹配或都需要相同的转换序列时,才会优先选择非模板版本。然而,在这种情况下,编译器似乎没有将操作符模板视为完美匹配。当同时存在 boolint 重载时,它自然认为它们是有歧义的。

总之,我的问题是为什么在这种情况下操作符模板不被视为完美匹配?

2个回答

11

让我们将其分解为两个不同的问题:

1. 为什么会生成编译器错误?

struct C
{
    operator bool () {return false;}
    operator int () {return 1;}
};

由于 intbool 都可以隐式转换为 double,编译器无法知道应该使用哪个函数。有两个函数可供选择,但它们之间没有优先级。

2. 为什么模板版本不是完美匹配?

struct C
{
    template <typename T> operator T () {return 0.5;}
    operator int () {return 1;}
};

为什么请求double时会调用operator int()
非模板函数被调用是因为在重载解析中非模板函数具有优先权。(重载函数模板编辑: 我错了!正如Yan Zhou在他的评论中提到的,并且如我所提供的链接所述,模板函数中的完美匹配优先于非模板函数。
我测试了您的代码(使用g ++ 4.7.2编译),并且它按预期工作:它返回0.5,换句话说,使用了模板函数! 编辑2: 现在我尝试使用clang,我可以复现您描述的行为。由于gcc可以正确运行,因此这似乎是clang中的一个bug。

我已经解决了问题中提到的第一部分。然而,对于第二部分,只有在其他方面相等时,非模板函数才具有优先权。在这种情况下,模板不需要转换,它返回一个double,而非模板运算符返回int或bool,然后需要转换为double。所以我仍然不明白为什么它不是完美匹配。即使在你给出的链接中,对f的第二次调用显示完美匹配模板具有优先权。非模板优先权是第一和第三个调用。 - Yan Zhou
@YanZhou,你说得对,我更新了我的答案。但是我的应用程序似乎与你的有所不同。我使用gcc进行了测试,目前正在安装clang进行比较。 - Misch
你用的是哪个GCC版本?我尝试了G++ 4.7,它给出了与Clang相同的错误,Intel icpc 13.1也是如此。 - Yan Zhou
我的错误,是G++4.2重现了问题,而G++4.7则没有任何抱怨。但我不认为这是clang的一个bug。Intel icpc 12.1和13.0都像clang一样产生了相同的错误,以及一些早期版本的gcc。由于只有最近的g++接受了这个代码,我只能假设这实际上是一个g++的问题。 - Yan Zhou

9
这很有趣。阅读13.3.3章节的关键部分有两种方式。原始示例肯定应该调用函数模板,但是删除其中一个非模板版本的版本可能被认为是有歧义的。
13.3.3:
可行函数F1被定义为比另一个可行函数F2更好,如果对于所有参数i,ICS_i(F1)不是比ICS_i(F2)更差的转换序列,然后
- 对于某些参数j,ICS_j(F1)是比ICS_j(F2)更好的转换序列,或者,如果不是这样, - 上下文是由用户定义的转换初始化(参见8.5、13.3.1.5和13.3.1.6),并且从F1的返回类型到目标类型(即正在初始化的实体的类型)的标准转换序列是从F2的返回类型到目标类型的标准转换序列更好,或者,如果不是这样, - F1是非模板函数,F2是函数模板特化,或者,如果不是这样, - F1和F2都是函数模板特化,而F1的函数模板根据14.5.6.2中描述的偏序规则更加专业化于F2的模板。
如果恰好有一个可行函数比所有其他可行函数都更好,那么它就是重载决议选择的函数;否则,调用是不良构的。
在示例中,clang正确地识别了三个可行的候选函数集:
C::operator int()
C::operator bool()
C::operator double<double>()

第三种是函数模板特化。(上面的语法可能不合法,但你可以理解为在重载决议的这一点上,它不被视为一个模板,而是具有明确函数类型的特化。)
在这里,参数的唯一隐式转换序列(ICS1)是精确匹配“左值C”到隐式参数“C&”,因此不会有任何影响。
这个例子恰好就是第二个子弹点描述的情况,因此返回double的函数明显比其他两个更好。
接下来是很奇怪的地方:经过非常文字的推断,“operator int”也比模板特化更好,因为满足了第三个子弹点。“等一下,‘better than’不应该是反对称的吗?你怎么能说F1比F2更好,而F2又比F1更好?”不幸的是,标准没有明确说明这样的事情。“第二个子弹点是否优先于第三个子弹点,因为有‘if not that’短语?”是的,对于常量F1和F2。但标准并未说明对于(F1,F2)满足第二个子弹点是否使得对于(F2,F1)第三个子弹点不适用。
当然,由于“operator int”既不比“operator bool”好也不比“operator bool”差,因此仍然存在“一个比所有其他可行函数更好的可行函数”。
我并不完全支持这种奇怪的解读,除非将其报告为标准缺陷。按照这种方式进行会产生奇怪的结果(例如,删除此示例中没有最佳的重载将使程序从良好形式变为模糊不清!)。 我认为意图是在考虑第三个子弹点之前,应该两次考虑第二个子弹点。
这意味着函数模板应该通过重载决议被选中,并且这是clang的错误。

你对标准的“怪异阅读”非常有趣,实际上对我来说很有意义。如果我理解正确的话,操作符 int 是否比模板更好取决于哪个是 F1 和哪个是 F2。如果我们考虑 F1 = op int,F2 = op<double>,那么在第三个步骤中,我们确定 F1 比 F2 更好并停止。但是,如果我们考虑 F1 = op<double>,F2 = op int,则在第二步中,我们确定 F1 更好并停止。但是,如果它们有三个,那么 op int 和 op bool 就不再比其他操作符更好,因此就不会出现歧义。 - Yan Zhou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接