“上下文转换”如何实现?与“&&”和“||”运算符与用户定义的运算符重载结合使用?
来自 @Xeo 的优秀 c++-faq
问题:安全吗? bool 惯用法在 C++11 中已过时? 我了解到不再需要安全 bool 惯用法,因为用户定义的显式
转换为bool
将在 C++03 中需要安全 bool 的上下文中自动调用。
然而,重载 &&
、||
和 !
等运算符的能力似乎可以规避这一点。
除了提供到 bool
的转换之外,还需要 operator!
的情况很少见,operator&&
和 operator||< 也是如此。 /code>,但 C++ 表达式树实现(用于延迟执行和符号数学技术)确实需要覆盖这些。
当调用用户定义的运算符时是否会发生“上下文转换”?需要什么样的 SFINAE 咒语来确保 operator&&
或 operator||
的定义对于实现“safe bool”的类型和设计的类型都能正确工作为了“上下文转换”?
为了澄清,给出:
class uses_safe_bool
{
void f() {};
typedef void (uses_safe_bool::* safe_bool)();
public:
operator safe_bool() const { return (rand() & 1)? &uses_safe_bool::f: 0; }
};
class uses_explicit_bool
{
public:
explicit operator bool() const { return rand() & 1; }
};
template<typename T>
class deferred_expression
{
// Not convertible to bool
public:
T evaluate() const;
};
operator||
需要什么签名,以便以下表达式全部有效:
deferred_expression<bool> db;
uses_safe_bool sb;
uses_explicit_bool eb;
int i;
auto test1 = sb || db;
auto test2 = eb || db;
auto test3 = true || db;
auto test4 = false || db;
auto test5 = i || db;
这些使用不同的重载:
auto test6 = db || db;
deferred_expression<int> di;
auto test7 = di || db;
并且以下表达式在编译时被拒绝:
std::string s;
auto test7 = s || db;
std::vector<int> v;
auto test8 = v || db;
deferred_expression<std::string> ds;
auto test9 = ds || db;
From @Xeo's excellent c++-faq
question: Is the safe-bool idiom obsolete in C++11? I learned that the safe bool idiom is no longer needed, because an explicit
user-defined conversion to bool
will be automatically invoked in contexts where the safe bool was needed in C++03.
However, the ability to overload operators such as &&
, ||
and !
seems to circumvent this.
Cases where operator!
is necessary beyond provision of conversion to bool
are rare, as are operator&&
and operator||
, but C++ expression tree implementations (used for deferred execution and symbolic math techniques) do need to override these.
Does "contextual conversion" take place when a user-defined operator is being invoked? What sort of SFINAE incantation is needed to make sure that a definition of operator&&
or operator||
will work correctly both with types implementing "safe bool" and those designed for "contextual conversion"?
To clarify, given:
class uses_safe_bool
{
void f() {};
typedef void (uses_safe_bool::* safe_bool)();
public:
operator safe_bool() const { return (rand() & 1)? &uses_safe_bool::f: 0; }
};
class uses_explicit_bool
{
public:
explicit operator bool() const { return rand() & 1; }
};
template<typename T>
class deferred_expression
{
// Not convertible to bool
public:
T evaluate() const;
};
What signatures are required for operator||
such that the following expressions are all valid:
deferred_expression<bool> db;
uses_safe_bool sb;
uses_explicit_bool eb;
int i;
auto test1 = sb || db;
auto test2 = eb || db;
auto test3 = true || db;
auto test4 = false || db;
auto test5 = i || db;
these use a different overload:
auto test6 = db || db;
deferred_expression<int> di;
auto test7 = di || db;
and the following are rejected at compile-time:
std::string s;
auto test7 = s || db;
std::vector<int> v;
auto test8 = v || db;
deferred_expression<std::string> ds;
auto test9 = ds || db;
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
对于 C++03(安全布尔习惯用法)和 C++11(显式转换运算符),规则是相同的:不要为此重载布尔运算符(以免丢失短路行为以及默认值)工作得很好)。后者之所以有效,是因为内置布尔运算符的操作数可以进行上下文转换,例如 n3290 中的
&&
,5.14 逻辑 AND 运算符 [expr.log.and]:(强调我的,其他运算符的类似文本)
重载运算符是常规函数调用,因此不会发生上下文转换。确保您的重载布尔运算符始终通过重载解析来选择,然后就可以开始了。例如,这忽略了左值:
请注意,
template是这样的。 void operator||(Lhs&&, Rhs&&);
当任何一个操作数类型为类类型时,将通过 ADL 选择,无论 cv 限定符和的价值类别。The rule is the same for C++03 (safe-bool idiom) and for C++11 (explicit conversion operator): don't overload the boolean operators for this (so as to not lose short circuit behaviour, plus the defaults work just fine). The latter will work because the operands of the built-in boolean operators are eligible for a contextual conversion, for instance for
&&
from n3290, 5.14 Logical AND operator [expr.log.and]:(emphasis mine, similar text for other operators)
Overloaded operators are regular function calls, so no contextual conversion takes place. Make sure your overloaded boolean operators are always picked through overload resolution and you're good to go. For instance, this is neglecting lvalues:
Note that
template<typename Lhs, typename Rhs> void operator||(Lhs&&, Rhs&&);
will be selected via ADL when any one of the operand type is of class type, regardless of cv-qualifiers and of value-category.