Почему преобразование в ссылку мешает конвертации в bool?

Кажется, что если у меня есть оператор преобразования в ссылке, этот оператор будет иметь приоритет перед преобразованием в bool. Почему это происходит, и как я могу его исправить?

(Если это имеет значение, я использую GCC 4.5. Я проверил на ideone, что такое же поведение найдено в GCC-4.7.2.)

Предположим следующее:

class B {
protected:
    const int a_;
    int b_;
    B (int b, int a) : a_(a), b_(b) {}
public:
    operator bool () const { return b_ == a_; }
};

class D1 : public B {
public:
    D1 (int b = 0, int a = 0) : B(b, a) {}
    operator int () const { return b_; }
};

class D2 : public B {
public:
    D2 (int b = 0, int a = 0) : B(b, a) {}
    operator int & () { return b_; }
};

Затем предположим, что они используются в простой программе:

int main () {
    if (D1 d1a = D1('a', 'a')) std::cout << "d1a\n";
    if (D1 d1b = D1('b', 'a')) std::cout << "d1b\n";
    if (D2 d2a = D2('a', 'a')) std::cout << "d2a\n";
    if (D2 d2b = D2('b', 'a')) std::cout << "d2b\n";
    return 0;
}

Выход этой программы:

d1a
d2a
d2b

Обратите внимание, что d1b не находится на выходе, что означает, что преобразование в bool работало так, как я ожидал, для D1. Но для D2 кажется, что преобразование в ссылочный тип имеет приоритет над преобразованием bool. Почему это случилось? Есть ли простое изменение, которое я могу сделать для D2, чтобы преобразование bool имело приоритет в проверке if?

В настоящее время я использую D1 и добавляю к нему оператор присваивания для достижения поведения ссылки.

Ответ 1

Собственно, он не имеет ничего общего с int&, это вопрос const -ness:

operator bool () const { return b_ == a_; }
              /* ^^^^^ */
              /* vvvvv */
operator int & () { return b_; }

d2a - это D2, а не const D2, поэтому оператор неконстантного преобразования лучше подходит. Если вы напишете его как

operator const int & () const { return b_; }

вы получите ожидаемое поведение, см. http://ideone.com/vPPPYV.

Обратите внимание, что operator const int& не будет вмешиваться, даже если вы используете версии const ваших объектов, следующие строки все равно приведут к вашему ожидаемому поведению (см. http://ideone.com/DTE0xH):

if (const D1 d1a = D1('a', 'a')) std::cout << "d1a\n";
if (const D1 d1b = D1('b', 'a')) std::cout << "d1b\n";
if (const D2 d2a = D2('a', 'a')) std::cout << "d2a\n";
if (const D2 d2b = D2('b', 'a')) std::cout << "d2b\n";

Ответ 2

Это

D1 d1a = D1('a', 'a');
D1 d1b = D1('b', 'a');
D2 d2a = D2('a', 'a');
D2 d2b = D2('b', 'a');
if (d1a) std::cout << "d1a\n";
if (d1b) std::cout << "d1b\n";
if (d2a) std::cout << "d2a\n";
if (d2b) std::cout << "d2b\n";

печатает

d1a
d2a

для меня.

У вас есть

if (D2 d2a = D2('a', 'a')) std::cout << "d2a\n";
if (D2 d2a = D2('b', 'a')) std::cout << "d2b\n";

Что произойдет, если вы не используете одно и то же имя в обоих случаях? Там только d1a и d2a на выходе, если я заменю 4-е, если на

if (D2 d2b = D2('b', 'a')) std::cout << "d2b\n";