Неявное преобразование типа для пользовательского типа

Почему неявное преобразование типов не выполняется для пользовательского типа в следующем исходном коде?

Неявное преобразование типа в тип A должно произойти в закомментированной строке, но этого не произошло, и в этой строке возникла ошибка.

Я хотел бы знать грамматические правила и решения для этой ошибки.

#include <iostream>

using namespace std;

class A {
   int v;
public:
    A(int _v): v(_v) {}
    operator int (void) const {
        return v;
    }
    friend ostream& operator << (ostream& os, const A& s) {
        return os << "A: " << s.v;
    }   
};

class B {
    int x, y;   
public:
    B(int _x, int _y): x(_x), y(_y) {}
    operator A(void) const {
        return A(x+y);
    }
};

int main(void)
{
    B b(1,2);
    cout << A(b) << endl;
    cout << (A)b << endl;
    cout << b << endl;     // error --> why?
    return 0;
}

--> Дополнительный вопрос

Спасибо за ответ Сэма Варшавчика.

Определение класса A, как показано ниже, решает проблему.

class A {
   int v;
public:
    A(int _v): v(_v) {}
    operator int (void) const {
        return v;
    }
    friend ostream& operator << (ostream& os, const A& s);
};

ostream& operator << (ostream& os, const A& s){
    return os << "A: " << s.v;
}

//////////////

class A {
   int v;
public:
    A(int _v): v(_v) {}
    operator int (void) const {
        return v;
    }
    friend ostream& operator << (ostream& os, const A& s){
        return os << "A: " << s.v;
    }
};

ostream& operator << (ostream& os, const A& s);

Объявлять ли «оператора ‹‹» другом, похоже, здесь не имеет значения. Поскольку функция является глобальной функцией, на нее можно ссылаться в других классах или других функциях. Я думаю, имеет значение, находится ли блок, определяющий функцию, внутри или снаружи класса A. Почему при использовании внутреннего определения необходимо объявление функции? Я хочу знать грамматическую основу.


person mzeff    schedule 15.03.2020    source источник
comment
Я вижу, к чему вы клоните, и вы, вероятно, уже это знаете, но будущие читатели: будьте осторожны с актерским составом в стиле C (A)b. Приведение в стиле C будет угрожать b как A независимо от того, имеет ли это смысл или нет. Здесь это имеет смысл. Так будет не всегда. Это было бы хорошим местом для static_cast. static_cast следит за тем, чтобы актерский состав имел смысл.   -  person user4581301    schedule 15.03.2020
comment
Что такое сообщение об ошибке? Подсказка: это, вероятно, говорит о том, что преобразование неоднозначно.   -  person Pete Becker    schedule 15.03.2020
comment
Я также использую static_cast в реальном процессе разработки. Я надеюсь, что студенты, которые увидят этот код, вспомнят совет пользователя 4581301. Однако, если вы до сих пор не знаете о static_cast, можете пропустить его совет. Этот код предназначен только для новичков.   -  person mzeff    schedule 15.03.2020
comment
Питу Беккеру: Это не ошибка из-за двусмысленности преобразования. Спасибо за внимание.   -  person mzeff    schedule 15.03.2020


Ответы (2)


Это должно иметь какое-то отношение к тайным правилам, связанным с отношениями между разрешением перегрузки, дружественными функциями, и неявными преобразованиями. Потому что здесь речь идет не только о неявных преобразованиях. Вы также определяете перегрузку operator<<, которая является дружественной функцией.

Просто избавившись от функции друга, следующий код отлично компилируется на gcc 9.2. Также не помешает избавиться и от using namespace std;:

#include <iostream>

class A {
   int v;
public:
    A(int _v): v(_v) {}
    operator int (void) const {
        return v;
    }
};

std::ostream& operator << (std::ostream& os, const A& s) {
    return os << "A: " << (int)s;
}

class B {
    int x, y;
public:
    B(int _x, int _y): x(_x), y(_y) {}
    operator A(void) const {
        return A(x+y);
    }
};

int main(void)
{
    B b(1,2);
    std::cout << A(b) << std::endl;
    std::cout << (A)b << std::endl;
    std::cout << b << std::endl;     // No more error
    return 0;
}
person Sam Varshavchik    schedule 15.03.2020
comment
Использование std:: для всех объектов может увеличить длину кода, что сделает его менее читаемым. Я думаю, что для удобочитаемости будет полезно опустить std, который является пространством имен стандартной библиотеки, даже если другие пространства имен опущены. - person mzeff; 15.03.2020
comment
Вам не нужно искать эти вопросы снова для меня. Думаю, ответ на подобную ошибку каждый раз может вас утомить. Я думаю, вы хороший человек, который сожалеет о таких ошибках и старается их не допускать. И ваши усилия обязательно помогут развитию мира. - person mzeff; 15.03.2020
comment
Однако, даже если новичкам легко ошибиться, очень важно улучшить читаемость кода. Даже если ваш вопрос затоплен незрелым использованием пространств имен некоторыми новичками, не кажется умной идеей не использовать преимущества синтаксиса C++. Скорее, это лучший способ объяснить им, как правильно использовать пространства имен. - person mzeff; 15.03.2020

Когда дружественная функция определена внутри класса, ее можно найти только через ADL.

В ваших первых двух строках вы предоставляете A, поэтому ADL срабатывает и находит operator<<. При передаче B ADL не поможет найти ваш operator<<, поэтому преобразование никогда не рассматривается компилятором.

Еще немного информации о ADL.

person super    schedule 15.03.2020