“上下文转换”如何实现?与“&&”和“||”运算符与用户定义的运算符重载结合使用?

发布于 2024-12-06 03:29:07 字数 1759 浏览 1 评论 0原文

来自 @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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

仅此而已 2024-12-13 03:29:07

对于 C++03(安全布尔习惯用法)和 C++11(显式转换运算符),规则是相同的:不要为此重载布尔运算符(以免丢失短路行为以及默认值)工作得很好)。后者之所以有效,是因为内置布尔运算符的操作数可以进行上下文转换,例如 n3290 中的 &&,5.14 逻辑 AND 运算符 [expr.log.and]:

1 &&运算符组从左到右。 操作数均根据上下文转换为 bool 类型(第 4 条)

(强调我的,其他运算符的类似文本)


重载运算符是常规函数调用,因此不会发生上下文转换。确保您的重载布尔运算符始终通过重载解析来选择,然后就可以开始了。例如,这忽略了左值:

struct evil {
    explicit operator bool() const;
};
void operator||(evil&&, evil&&);

evil e;
// built-in operator||
e || e;

// overloaded operator||
evil() || evil()

请注意,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]:

1 The && operator groups left-to-right. The operands are both contextually converted to type bool (Clause 4).

(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:

struct evil {
    explicit operator bool() const;
};
void operator||(evil&&, evil&&);

evil e;
// built-in operator||
e || e;

// overloaded operator||
evil() || evil()

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文