17

If the following test-programm

#include <iostream>

class A {
public:
    A() {}
    explicit operator bool() const {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        return true;
    }
//    explicit operator bool() {
//        std::cout << __PRETTY_FUNCTION__ << std::endl;
//        return true;
//    }
    const operator int() const {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        return 1;
    }
    operator int() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        return 1;
    }
};

int main() {
    A a;
    if (a) {
        std::cout << "bool()" << std::endl;
    }
    if (a + 0) {
        std::cout << "int()" << std::endl;
    }
}

is run, the output is

int A::operator int()
bool()
int A::operator int()
int()

and not

bool A::operator _Bool()
bool()
int A::operator int()
int()

what I expected (and what you get if you uncomment the commented parts).

So the question is what are the rules giving the conversion to non-const-int precedence over converting to const-bool?

wimalopaan
  • 4,838
  • 1
  • 21
  • 39

2 Answers2

11

When performing overload resolution on a reference binding, the less cv-qualified type is preferred. This is discussed in 13.3.3.2p3, with the example given:

struct X {
  void f() const;
  void f();
};
void g(const X& a, X b) {
  a.f(); // calls X::f() const
  b.f(); // calls X::f()
}

Note that binding an object to the implicit object parameter of a member function (13.3.1.1.1p2) is a reference binding (13.3.3.1.4).

Conversion operators are treated as member functions (13.3.1.5) for the purposes of overload resolution (13.3p2). Contextual conversion to bool has the semantics of initialization (4p4).

Importantly, any conversion required on the return type of the conversion operator is considered only after considering overload resolution between the conversion operators themselves (13.3.3p1).

The solution is to ensure that all conversion operators have the same const-qualification, especially to scalar type.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • *"Importantly, any conversion required on the return type of the conversion operator is considered only after considering overload resolution between the conversion operators themselves (13.3.3p1)."* Hmm there's the bullet that starts with *"the context is an initialization by user-defined conversion"* which seems to imply this was fully intended. +1 – dyp Feb 27 '14 at 12:52
  • @dyp there isn't a UDCS here, because we're not resolving between different functions with an `X` argument. There would be UDCSs if we had `f(bool); f(float); X x; f(x);` and indeed those UDCSs are ambiguous. – ecatmur Feb 27 '14 at 12:52
  • The whole conversion from `A` to `bool` is a UDCS (in the OP's example), but nevertheless the overload resolution is applied "too early" IMHO, where the UDC required to convert from `A` to `bool` isn't considered, but only used to define the viable functions (so only the SCS for the implied object argument is considered for overload resolution). – dyp Feb 27 '14 at 12:53
  • Hmm your example might be the *reason* why overload resolution of the conversion function happens so early: If the context is the call to an overloaded function (and not like in the OP, a conversion with a fixed target type), there are two overload resolution steps to perform: 1. selecting a conversion function 2. selecting an overload of `f`. – dyp Feb 27 '14 at 13:01
4

what are the rules giving the conversion to non-const-int precedence over converting to const-bool?

Using const member function of a non-const object, requires conversion of a non-const object into const-object. That is why operator int() has a better match over operator bool() const.

To make it a bit clearer, if you would remove int operators, what really happens with the first bool context (first if) is this :

if ( const_cast<const A&>(a).operator bool() ) {

Instead, what happens is this :

if ( a.operator int() )
BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • 1
    This answer doesn't really work out since the commented operator in the OPs question is also explicit and changes the behaviour just because of `const`. Also, the explicit `operator bool` can be used without casting in a boolean context (such as `if`). This is a very easy way to implement the safe bool idiom in C++11. – Excelcius Feb 27 '14 at 10:19
  • So to use the expilicit-bool-idiom in c++11 correctly one has to implement both the const and non-const version of operator bool(). Just to prevent other conversion operators to kick in? – wimalopaan Feb 27 '14 at 11:25
  • I don't quite understand how the ranking happens for this overload resolution. Typically, binding some `A` to an `A const&` is direct reference binding, and therefore considered an Exact Match. Therefore, the implied object argument should match exactly the implicit object parameter in both cases. Then, there's a UDC for both, but for the conversion to `int`, a Boolean Conversion follows. Clearly, that's not what's happening in the compilers, the matching of the implied object argument is somehow ranked differently.. – dyp Feb 27 '14 at 11:57
  • 1
    @wimalopaan Either that, or implement only const version of conversion operators. – BЈовић Feb 27 '14 at 11:57
  • @dyp I am trying to figure out which paragraph in the standard is important. I think it is [conv.quel], but it talks about pointers - not references. It still has to convert from lvalue into prvalue to call the `operator bool() const`, making `operator int()` more qualified. – BЈовић Feb 27 '14 at 12:10
  • The rank of a conversion sequence required to bind to a reference is described in [over.ics.ref]. Pointers are different from references for overload resolution! If we just considered some functions `void foo(A&); void foo(A const&);`, then [over.ics.rank] would disambiguate between those two. However, I think in the OP's example, we have ONE conversion sequence: a UDC, which *contains* a standard conversion sequence. It seems as if the part in [over.ics.rank] about reference bindings as Standard Conversion Sequences still applies, which I don't understand, as we have a UDC here. – dyp Feb 27 '14 at 12:19
  • @wimalopaan: "So to use the explicit-bool-idiom in c++11 correctly one has to implement both the const and non-const version of operator bool(), just to prevent other conversion operators to kick in?" @BЈовић: "Either that, or implement only const version of conversion operators." I'd have said, "Either that, or implement no other [implicit] conversion operators." `explicit operator bool() const` has no need to outcompete an `operator int()` if you do not write any `operator int()` for your type! – Quuxplusone Feb 04 '20 at 13:58