Использование библиотеки STL
Проблемы с компиляцией программ под STL
и их красивое решение в GNU C++
Следует сделать важное замечание о компиляции программ в STL. Дело в том, что STL распространяется в исходных кодах (это существенно влияет на производительность результирующего кода), поэтому зачастую строка с ошибкой будет указывать не на ваш код, а на внутренности STL. При этом строка с описанием ошибки будет занимать несколько сотен символов, что не сразу проливает свет на истинную причину её возникновения. Разберём только один простой пример.
Пусть мы передаём vector<int> как const reference параметр (как это и следует делать) в некую функцию. А в этой функции мы работаем с этим массивом посредством итераторов:
void f(const vector<int>& v) { for( vector<int>::iterator it = v.begin(); // ну что же здесь не так ... ... }В этом коротком участке кода есть ошибка. Вы можете её обнаружить?
Дело в том, что из немодифицируемого (const) объекта мы пытаемся получить модифицируемый (non-const) итератор при помощи функции begin(). Подобное преобразование является недопустимым. Корректный код выглядит следующим образом:
void f(const vector<int>& v) { int r = 0; // используется const_iterator for(vector<int>::const_iterator it = v.begin(); it != v.end(); it++) { r += (*it) * (*it); } return r; }В свете всего вышесказанного, имеет смысл знать про одну очень интересную функцию компилятора GNU C++. Заметим, что данная функция не входит в cтандарт C++, поэтому в большинстве других компиляторов её нет.
Итак, «оператор» typeof(x). На этапе компиляции он заменяется на тип выражения в скобках. Пример:
typeof(a+b) x = (a+b);Переменная x будет иметь тип, соответствующий типу выражения a+b. Напомним, что typeof(c.size()) -- unsigned int для всех контейнеров STL. Но наибольшую пользу можно извлечь из typeof в следующем контексте:
#define tr(container, iterator) \ for(typeof(container.begin()) iterator=container.begin(); \ it != container.end(); it++)Данный макрос -- сокращение от traverse -- будет работать даже для самого сложного типа контейнера, независимо от того, каким образом этот контейнер к моменту использования определён. Для const-объектов он породит const_iterator'ы:
void f(const vector<int>& v) { int r = 0; tr(v, it) { r += (*it)*(*it); } return r; }
Подобные ухищрения не столь важны при работе с векторами, но они совершенно незаменимы при работа с более сложными контейнерами. Это чувствуется особенно хорошо, когда код принимает примерно следующий вид:
void operate(const map< set< pair<int,int> >, vector< pair< double, pair<int,int> > > > &object) { for( ... ) // MAMMA MIA! }
Хотя, например, такая операция, как подсчитать сумму всех double из правой части map с использованием макроса tr выполняется сравнительно просто:
void operate(const map< set< pair<int,int> >, vector< pair< double, pair<int,int> > > > &c) { double result = 0; tr(c, it) { tr(it->second, it2) { result += it2->first; } } return result; }