18

考虑以下简单示例

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重载时,它自然会认为它们是模棱两可的。

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

4

2 回答 2

10

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

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;}
};

为什么operator int()在请求双精度时调用?

调用非模板函数是因为非模板函数在重载决议中具有优先权。(重载函数模板

编辑: 我错了!正如 Yan Zhou 在他的评论中提到的那样,正如我提供的链接中所述,模板化函数中的完美匹配优先于非模板化函数。

我测试了你的代码(用 g++ 4.7.2 编译),它按预期工作:它返回0.5了,换句话说,使用了模板函数!

EDIT2: 我现在尝试使用clang,我可以重现您描述的行为。由于它在 gcc 中正常工作,这似乎是 clang 中的一个错误。

于 2013-02-20T18:52:09.120 回答
7

这很有趣。有两种方法可以阅读第 13.3.3 节的关键部分。原始示例绝对应该调用函数模板,但删除其中一个非模板的版本可能会被认为是模棱两可的。

13.3.3:

如果对于所有参数i, ICS_i( ) 不是比 ICS_i( ) 更差的转换序列,则一个可行函数F1被定义为比另一个可行函数更好的函数,然后F2F1F2

  • 对于某些参数j,ICS_j( F1) 是比 ICS_j( F2) 更好的转换序列,或者,如果不是,

  • 上下文是通过用户定义的转换(参见 8.5、13.3.1.5 和 13.3.1.6)F1进行的初始化,并且从比从返回类型到目标类型的标准转换序列更好的转换序列F2,或者,如果不是,

  • F1是一个非模板函数并且F2是一个函数模板特化,或者,如果不是,

  • F1F2是函数模板特化,根据 14.5.6.2 中描述的部分排序规则,函数模板 forF1比模板 for 更特化。F2

如果恰好有一个可行函数比所有其他可行函数更好,那么它就是重载决议选择的那个;否则调用格式不正确。

在示例中,clang 正确识别了三个可行的候选函数集:

C::operator int()
C::operator bool()
C::operator double<double>()

第三是功能模板专业化。(我不认为上面的语法是合法的,但你明白了:在重载决议的这一点上,它不被视为模板,而是作为具有明确函数类型的特化。)

此处参数的唯一隐式转换序列 (ICS1) 是隐式参数上的“左值C”与“ C&”完全匹配,因此不会产生影响。

这个例子正是第二个项目中描述的情况,所以返回的函数double明显优于其他两个。

这就是它变得奇怪的地方:通过非常字面的阅读,operator int也比模板专业化更好,因为第三个项目符号。“等一下,‘比’不应该是反对称的吗?你怎么能说F1F2F2比好F1?” 不幸的是,该标准没有明确说明任何此类内容。“第二颗子弹不是因为‘如果不是那个’短语而优先于第三颗子弹吗?” 是的,对于常数F1F2。但是该标准并没有说满足第二个项目符号(F1,F2)会使第三个项目符号(F2,F1)不适用。

当然,由于operator int不优于operator bool,反之亦然,仍然存在“恰好一个可行的功能比所有其他可行的功能更好的功能”。

我并不完全赞同这种奇怪的阅读,除了可能将其报告为标准缺陷。这样做会产生奇怪的后果(比如删除这个例子中不是最好的重载会使程序从格式良好变为模棱两可!)。我认为目的是在考虑第三个项目符号之前从两个方面考虑第二个项目符号。

这意味着应该通过重载决议选择函数模板,这是一个clang错误。

于 2013-02-20T21:26:11.230 回答