08 апреля 2009

Range concatenation

В библиотеках для работы с итераторами (Boost.Iterators, Boost.Range, RangeEx) куча полезных вещей, но почему-то нигде не нашел механизма "склеить" два диапазона в один. Такого, чтобы можно было сделать вот так:
template <class Range> void process_range(Range const &);

std::vector<foo> v = ...;
std::list<foo> l = ...;
process_range(concat(v, l));
Вот разбить диапазон на части - пожалуйста, перемещать значения в случайном порядке - да не вопрос, а вот склеить - нифига.

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

С использованием boost::variant все получилось достаточно компактно, хотя не максимально эффективно:
template <class TIterator1, class TIterator2, ...>
class ConcatIterator : public boost::iterator_facade<...>
{
public:
reference dereference() const
{
return boost::apply_visitor(DereferenceVisitor<reference>(), It);
}

void increment()
{
boost::apply_visitor(IncrementVisitor(), It);
StabilizeForward();
}

void decrement()
{
StabilizeBackward();
boost::apply_visitor(DecrementVisitor(), It);
}

bool equal(const ConcatIterator& lhs) const
{
return (Range == lhs.Range) && (It == lhs.It);
}

private:
size_t Range; // 0 for the first range, 1 for the second range
boost::variant<TIterator1, TIterator2> It, End1, Begin2;
};

PS. Нет худа без добра. Мой велосипед вдохновил разработчиков Boost.Range добавить объединение диапазонов в RangeEx.

04 апреля 2009

Unexpected side effect

У каждого windows-процесса есть такая "глобальная переменная" - текущая директория. Её можно прочитать функцией GetCurrentDirectory() и изменить функцией SetCurrentDirectory().

Вот чего я не ожидал, что её меняют кто попало. Например, она может изменится после вызовов GetSaveFileName(), GetOpenFileName(). В их документации такое поведение описано и дана возможность его отключить. Но другое дело, что она может измениться внутри... функции StartDoc(). Это такая функция, которая "The StartDoc function starts a print job.", вобщем начинает печать.

И вот почему такое может происходить: драйвер принтера Microsoft Office Document Image Writer - поставляется с Microsoft Office и "печатает" в TIFF-файлы. При получении новой задачи он спрашивает куда же нужно сохранить создаваемый файл. Делает он это, я полагаю, с помощью GetSaveFileName(). Та, в свою очередь, меняет текущую директорию. Я бы сказал что врядли рядовой программист будет ожидать такие побочные эффекты от функции StartDoc().

Мораль (кроме той, что нужно драйвер подправить, добавив флажок OFN_NOCHANGEDIR) - старайтесь не менять глобального окружения кроме случаев, где это будет очевидно.