4

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;
Community
  • 1
  • 1
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720

1 Answers1

4

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.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • But what happens when operators ARE overloaded? Don't try to avoid the question, there are some cases (as mentioned in the question) where operator overloading is necessary. – Ben Voigt Sep 25 '11 at 04:04
  • @Ben Does the bit I added cover the sort of thing you wanted to know? – Luc Danton Sep 25 '11 at 04:32
  • Partly. One wouldn't provide a conversion to bool for EDSL types (probably), but one might use an EDSL type along with another type in the same expression. – Ben Voigt Sep 25 '11 at 04:44
  • @Ben The 'worst' thing that can happen is that some boolean expression will compile using the built-in operators. Without the conversion to bool (regardless of the technique used for the conversion), that expression would not compile. So a conversion won't 'steal' expressions from the overloaded operators, and arguably any such expressions should be a flaw in the EDSL. EDSL already have to mind converting constructors for binary operators for instance. – Luc Danton Sep 25 '11 at 04:55
  • But using the new "explicit" paradigm, a converting constructor in the EDSL type from `bool` won't be a candidate. – Ben Voigt Sep 25 '11 at 18:48
  • @BenVoigt That's not the point. My point was that EDSL are (or should be) already mindful of even more 'dangerous' conversions that are already here. So throwing down the new conversions won't break things for a well-designed EDSL. (`explicit` is a keyword, not a paradigm.) – Luc Danton Sep 25 '11 at 19:01
  • An EDSL designed for C++98 or C++03 would expect safe bool to be an implicit conversion. The new recommended technique makes it an explicit conversion, which could break existing code. What's the right way to write operators that are compatible with either safe bool (implicit conversion) or explicit conversion? – Ben Voigt Sep 25 '11 at 20:58
  • @BenVoigt There are conversions *other* than the safe-bool idiom that can even not be implicit, namely `explicit` converting constructors. *Those* are the conversions I'm speaking of that an EDSL must already take into account. As to how to write an operator, I've provided an example and the justification why I think it's adequate (it will match anything as soon as ADL kicks in). – Luc Danton Sep 25 '11 at 21:10