template
std::stringstream& operator%(std::stringstream& left, T right) {
left << right;
return left;
}
int main() {
std::stringstream out;
std::string val = "hello world";
int p = 100500;
out % '[' % val % " : " % p % ']';
std::cout << out.str();
return 0;
}
> а инклуд определения класса делать там, где без него уже никак - например там, где вызываются методы класса или создаются его экземпляры.
Чтобы создать экземпляр, компилятору нужно иметь определение класса. Чтобы вызвать метод экзмепляра, также нужно иметь определение класса (иначе компилятор не сможет понять, а есть ли вообще такой метод, и действительно ли у него такие формальные параметры).
Поймите, по сути разбиение на h/cpp это такой хак, необходимый из-за отсутствия (пока что) модульности в C++. Из-за того, что компилятор не умеет собирать информацию по нескольким файлам исходников (как в других языках), при компиляции конкретного cpp компилятору нужна ВСЯ необходимая информация для обработки исходного кода в этом cpp. Наличие этой информации достигается использованием инклудов - вы как бы просите препроцессор собрать для компилятора всю нужную информацию в один файл (который кстати будет намного толще в случае большой иерархии хедеров). Это и определения классов, и typedef-ы, и реализации ШАБЛОННЫХ методов и функций и т.д. После этого компилятор прочтет все нужные определения и сможет ими воспользоваться в нужных местах при обработке кода, находящегося в исходном cpp.
Однако, т.к. конечный бинарник будет линковаться линкером, то при компиляции отдельной "единицы компиляции" (т.е. нашего cpp) нет необходимости иметь определения обыкновенных (нешаблонных) функций и методов - ссылки на них будет ставить уже линковщик. Поэтому правило простое - все МАКСИМАЛЬНО выносится в "единицу компиляции", а та информация, без которой невозможно собирать остальной зависимый код - в хедер. Поэтому, например, реализации методов класса - в cpp, а определение самого класса - в хедере.
Из-за этого кстати появляются различные проблемы, например недостаточная абстракция реализации класса от интерфейса - т.к. экземпляры класса создаются не опосредованно через фабрики, а непосредственно по их определению в месте использования new, то при изменении определения класса (например, добавлении поля или изменении его типа) требуется перекомпиляция всего зависимого кода, т.к. может измениться размер объекта, и вообще нарушиться бинарная совместимость (т.е. DLL-ку/SO-шку уже просто так не поменяешь). Для решения этой проблемы используется, например, упомянутый Vitaly pImpl (там, где бинарную совместимость очень уж хочется). Есть и другие способы, например создание вынесение публичного интерфейса и создание пар функций-фабрик (create/destroy), чтобы переместить код создания/удаления внутрь нужного бинарника.