0

Есть два класса A и B, в классе A нет определения <<, но есть в классе B. Не хочет неявно приводить тип A к типу B, хотя, по идее, всё нормально. В чём проблема?

...
class B; 
class A
{
    public:
        friend class B;
        A(B& s_,...):s(s_),... {} // Конструктор с параметром типа B&
        ...
        operator B const& () const {return s;} // <-- приведение
        operator B& () {return s;} // <-- приведение
    private:
        B& s; 
        ...
 };
class B
{
    public:
        ... // всякие конструкторы и функции
        friend ostream& operator << (ostream &os, B const &s) 
        {
            ... // не вызывает подозрений
            return os;
        }
        friend class A;
        A operator [] (B const &s2) // метод, для которого и нужен класс A
        {
            ...
            return A(*this,...);
        }
}

В main пишу такое:

B s(...),s1(...);
cout << s[s1]; // <-- ошибка в этой строке

Появляется ошибка

[Error] cannot bind 'std::ostream {aka std::basic_ostream}' lvalue to 'std::basic_ostream&&'

Что интересно, если написать явное приведение, то всё норм:

cout << (B const& ) s[s1]; // <-- ошибки нет

P.s.: проблема именно в приведении, т.к. я написал << специально для A и всё работало.

mark113
  • 13
  • @Abyx Всё делается для метода этого. Просто до конца не ясно, какой тип возвращать и всё такое – mark113 Mar 01 '16 at 16:38
  • Это очень не безопасно кастить классы, которые не являются наследниками друг друга. Что будет если вы добавите в class A ещё члены или виртуальную функцию. Вы скорее всего получите неправильное поведение. – Unick Mar 01 '16 at 16:40
  • @Abyx Что именно? Реализацию <<? – mark113 Mar 01 '16 at 16:43
  • @mark113 Скиньте сюда минимальный компилируемый (во всем кроме ошибки с ostream) кусок кода, который воспроизводит данную проблему. – StateItPrimitive Mar 01 '16 at 16:50
  • @StateItPrimitive слишком много писать, да и не имеет особого смысла, так как все остальные части протестированы и работают нормально. Для этого мне нужно скинуть конструкторы (оба вызывают по несколько больших функций), реализацию метода, вводить все данные класса. Думаю, это скорее помешает – mark113 Mar 01 '16 at 16:53
  • @mark113 Вы можете создать тестовый проект, в котором вырезать все, кроме необходимых для воспроизведение ошибки частей (сделать везде заглушки, а не относящееся к проблеме вообще снести), который будет компилироваться при этом (т.е. без всяких ... в коде) не считая ошибки, указанной вами в вопросе. – StateItPrimitive Mar 01 '16 at 16:55
  • @StateItPrimitive я описал только минимум, нужный для инструкции cout << s[s1];. Как я это скомпилирую без конструкторов, [], всех данных и <<? – mark113 Mar 01 '16 at 16:57
  • @mark113 Например, вот так. Вынесите ostream& operator << (ostream &os, B const &s) из класса B. – StateItPrimitive Mar 01 '16 at 17:18
  • @StateItPrimitive код по ссылке не компилируется – mark113 Mar 01 '16 at 17:21
  • @mark113 Вот именно, и я написал что требуется сделать, чтобы он скомпилировался. Например, вот так. – StateItPrimitive Mar 01 '16 at 17:31
  • @StateItPrimitive скомпилилось! Спасибо! Так а в чём проблема тогда? Какая разница, где <<? – mark113 Mar 01 '16 at 17:31
  • @mark113 Думаю, что в разъяснении таких вещей лучше спросить @Abyx, т.к. я не уверен в своем ответе на 100%, поэтому пожалуй воздержусь от высказывания своей идеи :) @Abyx можете подсказать, что на самом деле делает компилятор, когда реализация оператора находится внутри класса с модификатором friend (раньше я никогда не пробовал реализацию функции писать там же где и само объявление другом внутри класса - я считал, что это вообще недопустимо!)? – StateItPrimitive Mar 01 '16 at 17:43
  • @mark113 Я оформлю в виде ответа тогда, чтобы данный вопрос не оставался в списке не отвеченных, ок? (когда мне придет в голову однозначное пояснение или другие пользователи SO помогут грамотно пояснить, то распишем в ответе). – StateItPrimitive Mar 01 '16 at 17:51
  • @StateItPrimitive конечно – mark113 Mar 01 '16 at 17:53
  • @Abyx Хорошо, спасибо – mark113 Mar 01 '16 at 18:19

2 Answers2

2

Функции определенные в теле класса при помощи friend не видны извне класса и могут быть найдены только при помощи ADL (поиска по типам аргументов).

Возьмем такой код:

struct A;

struct B {
    operator A();
};

struct A {
    friend void f(A) {}
    friend void g(B) {}
};

B::operator A(){ return A(); }

int main() {
    A a;
    B b;

    f(a); // OK
    g(b); // Ошибка

    f(b); // Ошибка
}

При вызове f(a), функция f успешно находится т.к. она находится в наборе имен A (хотя ее полное имя это ::f, но в процессе поиска имен можно считать что она находится в наборе имен A).

При вызове g(b), происходит ошибка, т.к. g в A не видна, а в других пространствах имен такой функции нет (ни в B, ни в глобальном).

При вызове f(b), происходит та же ошибка, т.к. f в A не видна, а в других пространствах имен ее нет (ни в B, ни в глобальном). Оператор приведения типа тут просто не успевает сработать, т.к. компилятор даже не знает что b надо приводить к какому-то типу.

Так как в коде приведенном в вопросе ADL не возможен, то тело operator<< надо вынести из класса (можно оставить его другом).

Abyx
  • 31,143
  • Самое интересное в этом примере то, что студийный компилятор (по крайней мере от visual studio 2010 и 2013) умудряется сделать неявные приведение типов и добраться до функции в единственном пространстве имен, где она реализована, в то время как gcc и clang выдают ошибку компиляции. – StateItPrimitive Mar 01 '16 at 19:47
  • Причем, если скопировать реализацию, например, функции g(B) в класс B, то вылетает ошибка в духе error: redefinition of 'g' во всех трех компиляторах (от MSVS, gcc и clang). Начинает складываться впечатление, что это не ADL, как будто, ведь ADL должно влиять лишь на поиск имени функции, указанного в выражении вызова функции, т.е. может влиять на ошибки компиляции в духе неоднозначности вызова функции, но не должен бы оказывать влияния на компиляцию определений функций с одинаковым прототипом в разных пространствах имен (независимо от их вызова), либо я чего-то недопонимаю :) – StateItPrimitive Mar 01 '16 at 19:59
  • Хм, получается, что человек наткнулся на настолько узкий момент, что я очень удивлен. Но хорошо, что это выяснилось так сразу, буду знать на будущее :) – StateItPrimitive Mar 01 '16 at 20:06
1

Вам необходимо вынести реализацию

ostream& operator << (ostream &os, B const &s)

за пределы класса B, при этом оставив объявление данного оператора другом класса B:

class B
{
   public:
      // ...
      friend ostream& operator << (ostream& os, const B& s);
      // ...
}

ostream& operator << (ostream& os, const B& s) 
{
   // ...
   return os;
}

Подробное разъяснение данной проблемы дано в ответе Abyx.


В следующий раз постарайтесь привести полный минимальный код (т.е. такой минимальный код, который скопировав в свой компилятор, любой пользователь SO сможет получить такую же ошибку), например такой, тогда мы сможем быстро предложить, например, такое решение.