Страницы

Поиск по вопросам

пятница, 13 марта 2020 г.

Неявное приведение типов в параметризованных классах

#cpp #шаблоны_с++


template  class 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* реализуете отдельно от класса, то проблема будет решена: template class 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.

Комментариев нет:

Отправить комментарий