Невозможно перегрузить оператор << в качестве функции-члена

Я пытаюсь перегрузить operator<< как функцию-член. Он работает, если просто сделать это:

friend ostream& operator<<(ostream& os, const MyClass& myClass); в моем файле заголовка и в файле MyClass.cc:

ostream& operator<<(ostream& os, const MyClass& myClass)
{
   return myClass.print(os);
}

Однако, если я попытаюсь отключить friend и сделать его функцией-членом, то он жалуется, что operator<< может принимать только один аргумент. Почему?

ostream& MyClass::operator<<(ostream& os, const MyClass& myClass)
{
   return myClass.print(os);
}

Я прочитал в этот вопрос, что он не может быть функцией-членом, но не уверен, почему?

Ответ 1

При перегрузке как функции-члена a << b интерпретируется как a.operator<<(b), поэтому он принимает только один явный параметр (с this как скрытый параметр).

Так как это требует, чтобы перегрузка была частью класса, используемого в качестве левого операнда, это не полезно с обычным ostream и таким. Это потребует, чтобы ваша перегрузка была частью класса ostream, а не частью вашего класса. Поскольку вам не разрешено изменять ostream, вы не можете этого сделать. Это оставляет только глобальную перегрузку в качестве альтернативы.

Существует, однако, довольно широко используемый шаблон, в котором вы перегружаете оператора глобально, но вызываете функцию-член:

class whatever { 
    // make this public, or the global overload a friend.
    std::ostream &write(std::ostream &dest) const { 
        // write self to dest
    }
};

std::ostream &operator<<(std::ostream &os, whatever const &w) { 
     return w.write(os);
}

Это особенно полезно, когда/если вы хотите полиморфное поведение. Вы не можете сделать перегруженный полиморфизм самого оператора, но вы вызываете функцию-член, которую он вызывает virtual, поэтому он все равно действует полиморфно.

Изменить: на (я надеюсь) прояснить ситуацию, вы можете сделать это несколькими разными способами. Первый и, вероятно, самый очевидный - просто сделать нашего участника write общедоступным и вызвать глобальный оператор. Поскольку это общедоступно, нам не нужно ничего делать, чтобы оператор использовал его:

class myClass {
public:
    std::ostream &write(std::ostream &os) const { 
        // write stuff to stream
        return os;
    }   
};

std::ostream &operator<<(std::ostream &os, myClas const &m) { 
   // since `write` is public, we can call it without any problem.
   return m.write(os);
}

Вторая альтернатива заключается в том, чтобы сделать write private и объявить operator<< другу, чтобы дать ему доступ:

class myClass {
    // Note this is private:
    std::ostream &write(std::ostream &os) const { 
        // write stuff to stream
        return os;
    }

    // since `write` is private, we declare `operator<<` a friend to give it access:
    friend std::ostream &operator<<(std::ostream &, myClass const &);
};

std::ostream &operator<<(std::ostream &os, myClas const &m) { 
   return m.write(os);
}

Есть третья возможность, что почти как вторая:

class myClass {
    // Note this is private:
    std::ostream &write(std::ostream &os) const { 
        // write stuff to stream
        return os;
    }

    // since `write` is private, we declare `operator<<` a friend to give it access.
    // We also implement it right here inside the class definition though:
    friend std::ostream &operator<<(std::ostream &os, myClas const &m) { 
        return m.write(os);
    }
};

Этот третий случай использует довольно странное (и малоизвестное) правило в С++ под названием "инъекция имени". Компилятор знает, что функция friend не может быть частью класса, поэтому вместо определения функции-члена это "вводит" имя этой функции в окружающую область (глобальная область, в данном случае). Хотя operator<< определяется внутри определения класса, он не является функцией-членом вообще - это глобальная функция.

Ответ 2

Вы можете перегрузить operator<< как функцию-член. Но вы не можете написать член operator<<, который берет ostream с левой стороны, а ваш класс - с правой стороны.

Когда вы делаете что-то (не статическую) функцию-член, подразумевается первый аргумент - вызывающий объект. operator<< является двоичным, поэтому он принимает только 2 аргумента. Если вы сделаете его функцией-членом, вы можете дать только один пареметр, потому что он уже имеет один (вызывающий объект). И поскольку этот вызывающий объект всегда является первым аргументом, вы не можете записать оператор вывода в качестве (нестатического) члена (по крайней мере, не в стандартной форме для этой конкретной функции), поскольку в этом случае ostream должен быть первым аргументом.

Ответ 3

Подумайте об этом так: когда вы хотите передать поток, вы вызываете < < оператора на объект потока. И вам не разрешается напрямую изменять метод ostream 'private'. Поэтому вам нужно создать перегруженную версию и сделать ее другом.

Когда вы создаете свой собственный оператор < < метод в вашем классе, вы создаете < метод, который будет работать на вашем классе, а не на объекте. Я предполагаю, что вы можете держать внутренний поток внутри своего класса и потоком к нему, но вам будет сложно писать прикованные заявления следующим образом: a << b << c.