为什么这个涉及重载运算符和隐式转换的 C++ 表达式不明确?
Why is this C++ expression involving overloaded operators and implicit conversions ambiguous?
operator bool
在以下示例中中断了 operator<
的使用。谁能解释为什么 bool
在 if (a < 0)
表达式中与特定运算符一样相关,是否有解决方法?
struct Foo {
Foo() {}
Foo(int x) {}
operator bool() const { return false; }
friend bool operator<(const Foo& a, const Foo& b) {
return true;
}
};
int main() {
Foo a, b;
if (a < 0) {
a = 0;
}
return 1;
}
当我编译时,我得到:
g++ foo.cpp
foo.cpp: In function 'int main()':
foo.cpp:18:11: error: ambiguous overload for 'operator<' (operand types are 'Foo' and 'int')
if (a < 0) {
^
foo.cpp:18:11: note: candidate: operator<(int, int) <built-in>
foo.cpp:8:17: note: candidate: bool operator<(const Foo&, const Foo&)
friend bool operator<(const Foo& a, const Foo& b)
这里的问题是C++有两个选项来处理a < 0
表达式:
- 将
a
转换为bool
,并使用内置运算符<
将结果与0
进行比较(一次转换)
- 将
0
转换为 Foo
,并将结果与您定义的 <
进行比较(一次转换)
这两种方式都等同于编译器,所以会报错。
您可以通过在第二种情况下删除转换来明确这一点:
if (a < Foo(0)) {
...
}
这正是编译器告诉你的。
为编译器解决 if (a < 0)
的一种方法是使用您提供的 Foo(int x)
构造函数从 0 创建对象。
第二个是使用 operator bool
转换并将其与 int
(提升)进行比较。您可以在 Numeric promotions 部分阅读更多相关信息。
因此,它对编译器来说是模棱两可的,它无法决定你想要它走哪条路。
因为编译器无法在 bool operator <(const Foo &,const Foo &)
和 operator<(bool, int)
之间进行选择,这两者都适合这种情况。
为了解决这个问题,创建第二个构造函数explicit
:
struct Foo
{
Foo() {}
explicit Foo(int x) {}
operator bool() const { return false; }
friend bool operator<(const Foo& a, const Foo& b)
{
return true;
}
};
编辑:
好的,最后我得到了一个真正的问题:) OP 问为什么他的编译器提供 operator<(int, int)
作为候选人,尽管 "multi-step conversions are not allowed" 。
答案:
是的,为了调用 operator<(int, int)
对象 a
需要转换 Foo -> bool -> int
。 但是,C++标准实际上并没有说"multi-step conversions are illegal".
§ 12.3.4 [class.conv]
At most one user-defined conversion (constructor or conversion
function) is implicitly applied to a single value.
bool
到int
不是用户定义的转换,因此是合法的,编译器有完全选择权operator<(int, int)
作为候选人。
重点是:
首先,operator <
.
有两个相关的重载
operator <(const Foo&, const Foo&)
。使用此重载需要使用 Foo(int)
. 将文字 0
用户定义的转换为 Foo
operator <(int, int)
。使用此重载需要使用用户定义的 operator bool()
将 Foo
转换为 bool
,然后是 promotion 到 int
(这是,在标准语中,不同于转换,正如 Bo Persson 所指出的那样)。
这里的问题是:歧义从何而来?当然,第一个调用只需要一个用户定义的转换,比第二个调用更明智,后者需要一个用户定义的转换然后提升?
但事实并非如此。该标准为每个 候选人 分配了一个等级。但是,"user-defined conversion followed by a promotion" 没有排名。这与仅使用用户定义的转换具有相同的等级。简单(但非正式)地说,排名顺序看起来有点像这样:
- 完全匹配
- (仅)需要促销
- (仅)需要隐式转换(包括从 C 继承的 "unsafe" 如
float
到 int
)
- 需要用户定义的转换
(免责声明:如前所述,这是非正式的。当涉及多个参数时,它会变得更加复杂,而且我也没有提到引用或 cv 限定。这只是一个粗略的概述。)
因此,希望这可以解释为什么调用不明确。现在是如何解决这个问题的实际部分。 几乎从来没有 提供 operator bool()
的人希望它隐式地用于涉及整数算术或比较的表达式中。在 C++98 中,有模糊的解决方法,范围从 std::basic_ios<CharT, Traits>::operator void *
到 "improved" 更安全的版本,涉及指向成员的指针或不完整的私有类型。幸运的是,C++11 在隐式使用 operator bool()
后引入了一种更具可读性和一致性的方法来防止整数提升,即将运算符标记为 explicit
。这将完全删除 operator <(int, int)
重载,而不仅仅是 "demoting" 它。
正如其他人提到的,您还可以将 Foo(int)
构造函数标记为显式。这将产生消除 operator <(const Foo&, const Foo&)
过载的相反效果。
第三种解决方案是提供额外的重载,例如:
operator <(int, const Foo&)
operator <(const Foo&, int)
在本例中,后者作为精确匹配优先于上述重载,即使您没有引入 explicit
。同样的事情,例如对于
operator <(const Foo&, long long)
在 a < 0
中优于 operator <(const Foo&, const Foo&)
,因为它的使用只需要促销。
operator bool
在以下示例中中断了 operator<
的使用。谁能解释为什么 bool
在 if (a < 0)
表达式中与特定运算符一样相关,是否有解决方法?
struct Foo {
Foo() {}
Foo(int x) {}
operator bool() const { return false; }
friend bool operator<(const Foo& a, const Foo& b) {
return true;
}
};
int main() {
Foo a, b;
if (a < 0) {
a = 0;
}
return 1;
}
当我编译时,我得到:
g++ foo.cpp
foo.cpp: In function 'int main()':
foo.cpp:18:11: error: ambiguous overload for 'operator<' (operand types are 'Foo' and 'int')
if (a < 0) {
^
foo.cpp:18:11: note: candidate: operator<(int, int) <built-in>
foo.cpp:8:17: note: candidate: bool operator<(const Foo&, const Foo&)
friend bool operator<(const Foo& a, const Foo& b)
这里的问题是C++有两个选项来处理a < 0
表达式:
- 将
a
转换为bool
,并使用内置运算符<
将结果与0
进行比较(一次转换) - 将
0
转换为Foo
,并将结果与您定义的<
进行比较(一次转换)
这两种方式都等同于编译器,所以会报错。
您可以通过在第二种情况下删除转换来明确这一点:
if (a < Foo(0)) {
...
}
这正是编译器告诉你的。
为编译器解决 if (a < 0)
的一种方法是使用您提供的 Foo(int x)
构造函数从 0 创建对象。
第二个是使用 operator bool
转换并将其与 int
(提升)进行比较。您可以在 Numeric promotions 部分阅读更多相关信息。
因此,它对编译器来说是模棱两可的,它无法决定你想要它走哪条路。
因为编译器无法在 bool operator <(const Foo &,const Foo &)
和 operator<(bool, int)
之间进行选择,这两者都适合这种情况。
为了解决这个问题,创建第二个构造函数explicit
:
struct Foo
{
Foo() {}
explicit Foo(int x) {}
operator bool() const { return false; }
friend bool operator<(const Foo& a, const Foo& b)
{
return true;
}
};
编辑:
好的,最后我得到了一个真正的问题:) OP 问为什么他的编译器提供 operator<(int, int)
作为候选人,尽管 "multi-step conversions are not allowed" 。
答案:
是的,为了调用 operator<(int, int)
对象 a
需要转换 Foo -> bool -> int
。 但是,C++标准实际上并没有说"multi-step conversions are illegal".
§ 12.3.4 [class.conv]
At most one user-defined conversion (constructor or conversion function) is implicitly applied to a single value.
bool
到int
不是用户定义的转换,因此是合法的,编译器有完全选择权operator<(int, int)
作为候选人。
重点是:
首先,operator <
.
operator <(const Foo&, const Foo&)
。使用此重载需要使用Foo(int)
. 将文字 operator <(int, int)
。使用此重载需要使用用户定义的operator bool()
将Foo
转换为bool
,然后是 promotion 到int
(这是,在标准语中,不同于转换,正如 Bo Persson 所指出的那样)。
0
用户定义的转换为 Foo
这里的问题是:歧义从何而来?当然,第一个调用只需要一个用户定义的转换,比第二个调用更明智,后者需要一个用户定义的转换然后提升?
但事实并非如此。该标准为每个 候选人 分配了一个等级。但是,"user-defined conversion followed by a promotion" 没有排名。这与仅使用用户定义的转换具有相同的等级。简单(但非正式)地说,排名顺序看起来有点像这样:
- 完全匹配
- (仅)需要促销
- (仅)需要隐式转换(包括从 C 继承的 "unsafe" 如
float
到int
) - 需要用户定义的转换
(免责声明:如前所述,这是非正式的。当涉及多个参数时,它会变得更加复杂,而且我也没有提到引用或 cv 限定。这只是一个粗略的概述。)
因此,希望这可以解释为什么调用不明确。现在是如何解决这个问题的实际部分。 几乎从来没有 提供 operator bool()
的人希望它隐式地用于涉及整数算术或比较的表达式中。在 C++98 中,有模糊的解决方法,范围从 std::basic_ios<CharT, Traits>::operator void *
到 "improved" 更安全的版本,涉及指向成员的指针或不完整的私有类型。幸运的是,C++11 在隐式使用 operator bool()
后引入了一种更具可读性和一致性的方法来防止整数提升,即将运算符标记为 explicit
。这将完全删除 operator <(int, int)
重载,而不仅仅是 "demoting" 它。
正如其他人提到的,您还可以将 Foo(int)
构造函数标记为显式。这将产生消除 operator <(const Foo&, const Foo&)
过载的相反效果。
第三种解决方案是提供额外的重载,例如:
operator <(int, const Foo&)
operator <(const Foo&, int)
在本例中,后者作为精确匹配优先于上述重载,即使您没有引入 explicit
。同样的事情,例如对于
operator <(const Foo&, long long)
在 a < 0
中优于 operator <(const Foo&, const Foo&)
,因为它的使用只需要促销。