重载运算符的德摩根定律优化
De Morgan's Law optimization with overloaded operators
每个程序员都应该知道:
在某些情况下,为了优化程序,编译器可能会将(!p && !q)
修改为(!(p || q))
。
这两个表达式是等价的,计算第一个或第二个没有区别。
但在 C++ 中,可以重载运算符,而重载运算符可能并不总是遵守这一点 属性。所以这样改造代码实际上会修改代码。
当 !
、||
和 &&
重载时,编译器是否应该使用 De Morgan 定律?
我想你已经回答了你自己的问题:不,编译器不能这样做。不仅运算符可以重载,有些甚至不能定义。例如,您可以定义 operator &&
和 operator !
,而根本不定义 operator ||
。
请注意,还有许多编译器无法遵循的其他规律。比如不能把p||q
改成q||p
,也不能把x+y
改成y+x
。
(以上所有都适用于重载运算符,因为这就是问题所要求的。)
不,在那种情况下转换将无效。根据 as-if 规则,将 !p && !q
转换为 !(p || q)
的权限是隐式的。 as-if 规则允许任何粗略地说不能被正确程序观察到的转换。当使用重载运算符并检测到转换时,这自动意味着不再允许转换。
注意:
Builtin operators && and || perform short-circuit evaluation (do not evaluate the second operand if the result is known after evaluating the first), but overloaded operators behave like regular function calls and always evaluate both operands.
...
Because the short-circuiting properties of operator&& and operator|| do not apply to overloads, and because types with boolean semantics are uncommon, only two standard library classes overload these operators ...
Source: http://en.cppreference.com/w/cpp/language/operator_logical
(emphasis mine)
还有那个:
If there is a user-written candidate with the same name
and parameter types as a built-in candidate operator function, the built-in operator function is hidden and
is not included in the set of candidate functions.
Source: n4431 13.6 Built-in operators [over.built] (emphasis mine)
总而言之:重载运算符的行为类似于用户编写的常规函数。
不,编译器不会将对用户编写的函数的调用替换为对另一个用户编写的函数的调用。
否则可能会违反 "as if" 规则。
重载运算符本身只是函数调用的语法糖;不允许编译器本身对此类调用可能成立或不成立的属性做出任何假设。仅当 "real operators"被使用。
请注意 标准库的某些部分 可能会将一些特定的语义与重载运算符相关联 - 例如,std::sort
默认情况下需要一个 operator<
遵守元素之间严格的弱排序 - 但这当然列在每个 algorithm/container.
的先决条件中
(顺便说一句,无论如何都应该避免重载 &&
和 ||
,因为它们在过载时会失去短路特性,因此它们的行为会变得令人惊讶并因此具有潜在危险)
你问的是编译器是否可以任意重写你的程序来做你没有写的事情。
答案是:当然不是!
- 在德摩根定律适用的地方,它们可能适用。
- 他们没有的地方,他们可能没有。
就这么简单
不直接。
如果 p 和 q 是表达式,因此 p 没有重载运算符,则短路评估生效:仅当 p 为假时才对表达式 q 进行评估。
如果 p 是非原始类型,则没有短路评估,重载函数可以是任何东西 - 甚至与常规用法无关。
编译器将以自己的方式进行优化。也许它可能会导致 de Morgan 身份,但不会在 if 条件替换的级别上。
DeMorgan 定律适用于这些运算符的语义。重载适用于这些运算符的 语法 。无法保证重载运算符会实现 DeMorgan 定律适用所需的语义。
But in C++ it is possible to overload operators, and the overloaded operator may not always respect this property.
重载运算符不再是运算符,而是函数调用。
class Boolean
{
bool value;
..
Boolean operator||(const Boolean& b)
{
Boolean c;
c.value = this->value || b.value;
return c;
}
Boolean logical_or(const Boolean& b)
{
Boolean c;
c.value = this->value || b.value;
return c;
}
}
所以这行代码
Boolean a (true);
Boolean b (false);
Boolean c = a || b;
相当于这个
Boolean c = a.logical_or(b);
每个程序员都应该知道:
在某些情况下,为了优化程序,编译器可能会将(!p && !q)
修改为(!(p || q))
。
这两个表达式是等价的,计算第一个或第二个没有区别。
但在 C++ 中,可以重载运算符,而重载运算符可能并不总是遵守这一点 属性。所以这样改造代码实际上会修改代码。
当 !
、||
和 &&
重载时,编译器是否应该使用 De Morgan 定律?
我想你已经回答了你自己的问题:不,编译器不能这样做。不仅运算符可以重载,有些甚至不能定义。例如,您可以定义 operator &&
和 operator !
,而根本不定义 operator ||
。
请注意,还有许多编译器无法遵循的其他规律。比如不能把p||q
改成q||p
,也不能把x+y
改成y+x
。
(以上所有都适用于重载运算符,因为这就是问题所要求的。)
不,在那种情况下转换将无效。根据 as-if 规则,将 !p && !q
转换为 !(p || q)
的权限是隐式的。 as-if 规则允许任何粗略地说不能被正确程序观察到的转换。当使用重载运算符并检测到转换时,这自动意味着不再允许转换。
注意:
Builtin operators && and || perform short-circuit evaluation (do not evaluate the second operand if the result is known after evaluating the first), but overloaded operators behave like regular function calls and always evaluate both operands.
... Because the short-circuiting properties of operator&& and operator|| do not apply to overloads, and because types with boolean semantics are uncommon, only two standard library classes overload these operators ...
Source: http://en.cppreference.com/w/cpp/language/operator_logical (emphasis mine)
还有那个:
If there is a user-written candidate with the same name and parameter types as a built-in candidate operator function, the built-in operator function is hidden and is not included in the set of candidate functions.
Source: n4431 13.6 Built-in operators [over.built] (emphasis mine)
总而言之:重载运算符的行为类似于用户编写的常规函数。
不,编译器不会将对用户编写的函数的调用替换为对另一个用户编写的函数的调用。 否则可能会违反 "as if" 规则。
重载运算符本身只是函数调用的语法糖;不允许编译器本身对此类调用可能成立或不成立的属性做出任何假设。仅当 "real operators"被使用。
请注意 标准库的某些部分 可能会将一些特定的语义与重载运算符相关联 - 例如,std::sort
默认情况下需要一个 operator<
遵守元素之间严格的弱排序 - 但这当然列在每个 algorithm/container.
(顺便说一句,无论如何都应该避免重载 &&
和 ||
,因为它们在过载时会失去短路特性,因此它们的行为会变得令人惊讶并因此具有潜在危险)
你问的是编译器是否可以任意重写你的程序来做你没有写的事情。
答案是:当然不是!
- 在德摩根定律适用的地方,它们可能适用。
- 他们没有的地方,他们可能没有。
就这么简单
不直接。
如果 p 和 q 是表达式,因此 p 没有重载运算符,则短路评估生效:仅当 p 为假时才对表达式 q 进行评估。
如果 p 是非原始类型,则没有短路评估,重载函数可以是任何东西 - 甚至与常规用法无关。
编译器将以自己的方式进行优化。也许它可能会导致 de Morgan 身份,但不会在 if 条件替换的级别上。
DeMorgan 定律适用于这些运算符的语义。重载适用于这些运算符的 语法 。无法保证重载运算符会实现 DeMorgan 定律适用所需的语义。
But in C++ it is possible to overload operators, and the overloaded operator may not always respect this property.
重载运算符不再是运算符,而是函数调用。
class Boolean
{
bool value;
..
Boolean operator||(const Boolean& b)
{
Boolean c;
c.value = this->value || b.value;
return c;
}
Boolean logical_or(const Boolean& b)
{
Boolean c;
c.value = this->value || b.value;
return c;
}
}
所以这行代码
Boolean a (true);
Boolean b (false);
Boolean c = a || b;
相当于这个
Boolean c = a.logical_or(b);