#cpp #шаблоны_с++
templateclass B; template class A { public: friend class B ; operator B (); }; template class B { public: B operator * (const B &s2) const {} friend class A ; A operator [] (const B &s2) {} }; template 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') Почему не работает оператор приведения? UPD Оказывается, вот это компилится #include template class B; template std::ostream& operator << (std::ostream &os, const B &s) { } template class A { public: friend class B ; operator B (); }; template class B { public: friend class A ; A operator [] (const B &s2) {} friend std::ostream& operator << (std::ostream &, const B &); }; template B operator*(const A & s1, const B & s2) { return B (); } template A ::operator B () { return B (); } int main() { B s1, s2, s3; std::cout << s1[s2] * s3; } Так что, возможно, проблема с другим связана
Ответы
Ответ 1
Вся проблема заключается в ADL (Argument Dependent Lookup), если же вы operator* реализуете отдельно от класса, то проблема будет решена: templateclass B; template class A { public: friend class B ; operator B (); }; template class B { public: friend class A ; A operator [] (const B &s2) { return A (); } }; template B operator*(const A & s1, const B & s2){ return B (); } template A ::operator B (){ return B (); } int main() { B s1, s2, s3; s1[s2] * s3; } Пояснение Операторы, определенные в теле класса, не видны извне класса и могут быть найдены только при помощи ADL (поиска по типам аргументов). Оператор приведения типа тут просто не успевает сработать, т.к. компилятор даже не знает что экземпляр класса A необходимо привести к классу B (да и вообще, какому-либо классу), а реализация данного оператора находится именно внутри класса B. Ответ 2
Начнем с того, что наличие шаблонных классов для проявления проблемы совершенно не нужно. Т.к. все члены открыты, то для уменьшения кода можно заменить 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' and 'A ') Т.е. компилятор не может найти operator*, если оба операнда имеют тип A. Один из способов убрать эту ошибку - сделать оператор другом класса A: friend B operator*(const B &, const B &); Правда при этом в g++ появляется предупреждение, а clang вовсе отказывается компилировать. Если добавить явную реализацию для операндов типа A, ошибок и предупреждений не будет: template struct B; template struct A { operator B () const; friend B operator*(const A& lhs, const A& rhs) { return B (lhs) * rhs; } }; template struct B { A operator[](const B&) { return A (); } friend B operator*(const B&, const B&) { return B(); } }; template A ::operator B () const { return B (); } int main() { B s1, s2, s3; s1[s2] * s3; s3 * s1[s2]; s1[s2] * s1[s2]; s3 * s3; } Конечно, хотелось бы ограничиться одной реализацией operator* внутри B с возможностью неявного приведения типов. Не знаю, можно ли этого достичь в современном C++, создал соответствующий вопрос на эту тему, правда, на английской части SO.
Комментариев нет:
Отправить комментарий