Начнем с того, что наличие шаблонных классов для проявления проблемы совершенно не нужно. Т.к. все члены открыты, то для уменьшения кода можно заменить class на struct, а friend убрать вообще. Дополнительно я добавил возвращение значений там где это требовалось. Получился следующий код:
struct B;
struct A
{
operator B();
};
struct B
{
B operator*(const B&) const { return B(); }
A operator[](const B&) { return A(); }
};
A::operator B()
{
return B();
}
int main()
{
B s1, s2, s3;
s1[s2] * s3; // <-- ошибка
}
Можно убедиться, что ошибка осталась прежней:
error: no match for 'operator*' (operand types are 'A' and 'B')
Возвращаемся к самому вопросу:
Почему не работает оператор приведения?
Не работает потому, что имеющаяся перегрузка operator* реализована как функция-член. Для нестатических функций-членов действует правило, что первый параметр (т.е. this) не может быть получен путем неявного преобразования из другого типа. Например, если поменять аргументы местами, т.е. использовать выражение s3 * s1[s2];, код скомпилируется, т.к. на место левого операнда встал тип B, который не нужно преобразовывать, а правый операнд будет успешно неявно преобразован из типа A в B.
Чтобы исключить ошибку для исходного случая, надо реализовать operator* как свободную функцию:
B operator*(const B&, const B&) { return B(); }
Тогда неявные преобразования будут возможны для обоих операндов.
Однако, если итоговый вариант попытаться обобщить для шаблонных классов ошибка проявится вновь. Причина тому - запрет выполнения неявных преобразований для аргументов шаблонной функции, т.е. нашего бинарного operator*. Чтобы эту ситуацию подправить, нужно перенести реализацию оператора в тело класса, сделав саму функцию нешаблонной (но в шаблонном классе B), и сохранить возможность по-прежнему принимать 2 явных операнда. Т.е. она не должна стать нестатической функцией-членом. Всё это достигается не совсем стандартным способом, путем добавления слова friend (несмотря на отсутствие необходимости иметь доступ к приватным данным (более подробно об этом можно почитать здесь и здесь) ).
Получим такую запись:
friend B operator*(const B&, const B&) { return B(); }
Большая часть ошибок ушла, но одна, очень похожая на исходную, остаётся:
error: no match for 'operator*' (operand types are 'A<int>' and 'A<int>')
Т.е. компилятор не может найти operator*, если оба операнда имеют тип A.
Один из способов убрать эту ошибку - сделать оператор другом класса A:
friend B<T> operator*(const B<T>&, const B<T>&);
Правда при этом в g++ появляется предупреждение, а clang вовсе отказывается компилировать.
Если добавить явную реализацию для операндов типа A, ошибок и предупреждений не будет:
template <class T>
struct B;
template <class T>
struct A
{
operator B<T>() const;
friend B<T> operator*(const A& lhs, const A& rhs) { return B<T>(lhs) * rhs; }
};
template <class T>
struct B
{
A<T> operator[](const B&) { return A<T>(); }
friend B operator*(const B&, const B&) { return B(); }
};
template <class T>
A<T>::operator B<T>() const
{
return B<T>();
}
int main()
{
B<int> s1, s2, s3;
s1[s2] * s3;
s3 * s1[s2];
s1[s2] * s1[s2];
s3 * s3;
}
Конечно, хотелось бы ограничиться одной реализацией operator* внутри B с возможностью неявного приведения типов. Не знаю, можно ли этого достичь в современном C++, создал соответствующий вопрос на эту тему, правда, на английской части SO.
A<t_b> operator []иstd::ostream& operator <<ничего не возвращают, а должны бы) потому, что результатом выполнения операцииs1[s2] * s3(после исправления в коде ошибки ADL) является классB, аstd::ostream& operator <<, принимающий экземпляр классаB, описан в глобальном пространстве имен. Почему он не должен компилироваться (ведь в нем не проявляется проблема, связанная с ADL)? – StateItPrimitive Mar 15 '16 at 08:50friendу операторов. – StateItPrimitive Mar 15 '16 at 08:51