Правила сдачи:
-
контрольная выдается на несколько дней
-
сдается в один репозиторий, в разные ветки
-
есть 2 важных периода:
-
(A): первые 2 часа после выдачи КР
-
(B): последущее время до завершения работы
-
Блок (А) — почти как было:
-
длится первые 2 часа после выдачи КР
-
с вас — исключительно самостоятельное решение
-
нужно в этот срок нарешать задач для получения 0,5 баллов (ок сделать не все задачи, нужен минимум)
-
это необходимое условие для общего зачета контрольной
-
-
есть код-ревью — проводится после завершения КР
-
по завершению блока (A) оставьте в ревью (пулл-реквест master→feedback) сообщение с ссылкой на последний коммит, сделанный до окончания (А)
Блок (B):
-
длится после окончания блока (А) до завершения КР
-
с вас — самостоятельное решение + возможно разумное использование сторонних материалов и источников, влияющих на реализацию
-
подробнее:
-
если вы воспользовались сторонними материалами — сообщите об этом в ревью и приложите источник
-
если вы вдохновились реализацией/статьей из интернета — приложите ссылку на источник
-
если вы использовали ассистента/бота — приложите дамп вашего общения в репозиторий (например, через промт:
запиши наш разговор в markdown формате— сложите в файликchat.md) -
разумное использование подразумевает работу с открытыми источниками, а не, например, списывание готовой задачи
-
-
решение поместите в новую ветку feedback_offline (основываясь на master) и сделайте пул-реквест feedback_offline→master
-
есть код-ревью — проводится после завершения КР
-
штрафы из части (А) нельзя исправить фиксами во время периода (B)
-
Общее:
-
решение сдается в одном репозитории
-
с вас — несколько сообщений в пулл-реквесте
-
-
работа будет оцениваться в т.ч. по приватным тестам
-
в решении мы ожидаем:
-
идиоматичный код без неоптимальностей
-
что вы его понимаете и готовы про него рассказать/воспроизвести
-
-
располагайте решение в namespace
control_01, непубличные вещи — в подпространстве именdetails -
условия задачи даны в обратном порядке: task02, task01
-
рекомендуется первично решить task02 в (A)-части и task01 в (B)-части
-
Итого по шагам:
-
старт КР, блок (А):
-
решаем как обычно
-
-
через 2 часа — конец блока (A):
-
пушим задачек, чтобы набрать 0,5+ балла
-
в пулл-реквесте master→feedback (открывается автоматически гихабом) оставляем комментарий со ссылкой на последний коммит
-
-
сразу после — старт блока (B):
-
отводим ветку feedback_offline от master
-
решаем остальные задачи в этой ветке
-
-
через несколько дней — конец блока (B):
-
пушим последний коммит в feedback_offline
-
открываем PR feedback_offline→master
-
пишем в комментарии ссылки — чем пользовались
-
прикладываем разговоры с ботом в эту же ветку в файлик
chat.md
-
На Масленицу подают на стол тарелки с блинами и икрой
struct blin {};
struct ikra {};
template<class Sloy1, class... Sloi> struct tarelka {};Блинов и икры может быть сколько угодно (не менее 1 слоя), но есть физическое ограничение: два слоя икры подряд сделать нельзя (это же получится один толстый слой).
Напишите метафункцию pravilno_v, которая проверяет, правильно ли сформировали стопку блинов и икры (нет двойной икры)
template<class Tarelka> constexpr bool pravilno_v = ???;Напишите операцию сложения блинов и икры
auto bb = blin{} + blin{}; // tarelka<blin,blin>
auto bi = blin{} + ikra{}; // tarelka<blin,ikra>
auto bii = bi + ikra{}; // невозможно: две икры подряд!
auto ibi = ikra{} + bi; // tarelka<ikra,blin,ikra>
auto ibi1 = ikra{} + blin{} + ikra{}; // tarelka<ikra,blin,ikra>
auto bibb = bi + bb; // tarelka<blin,ikra,blin,blin>
auto biibi = bi + ibi; // невозможно: две икры подрядОшибка двойной икры должна быть сделана как SFINAE (не найдётся перегрузка оператора), а не как безусловная ошибка компиляции (например, static_assert внутри). Это нужно для тестирования.
Обратите внимание, что из-за ассоциативности операции сложения операндами и слева и справа могут оказаться и отдельный блин или икра, и тарелки.
Разработайте шаблонный класс zip, позволяющий итерироваться по набору нескольких контейнеров одновременно
std::vector<size_t> x{1,2,3};
std::string str = "hello";
auto zipper = zip(x, str); // zip<...>
auto it = zipper.begin(); // zip<...>::iterator (aka ZipIterator)
auto firstElements = *it; // zip<...>::iterator::value_type (aka ZipTuple)
std::cout << firstElements.get<0>(); // 1
std::cout << firstElements.get<1>(); // 'h'Необходимые методы zip:
-
конструкторы: от произвольного числа контейнеров, копирования
-
operator=-
move-версии не нужны (zip не владеет переданными контейнерами)
-
-
begin,end(const / non-const) -
cbegin,cend
Должны быть определены типы:
-
zip::iterator -
zip::const_iterator
zip::[const_]iterator должен поддерживать
-
operator* -
operator++(pre/post) -
operator==,operator!= -
operator→
Объект-итератор должен быть легковесным и не иметь стейта
Разыменованный итератор дает временный объект — ZipTuple:
-
использует
std::tuple, содержащий соответствующие элементы из всех контейнеров (ссылки, без владения) -
имеет дополнительный метод
get<size_t N>для доступа к N-му элементу кортежа контейнеров
Также реализуйте вспомогательную функцию make_zip (поддержка вывода типа до С++17 и CTAD), принимающую на вход произвольное число контейнеров и возвращающую сконструкированный от них zip
std::vector<size_t> x{1,2,3};
std::string str = "hello";
// выведет
// 1; h
// 2; e
// 3; l
for (auto&& pair : zip(x, str)) {
std::cout << pair.get<0>() << "; " << pair.get<1>() << std::endl;
}Как видим по предыдущему примеру, переданные zip контейнеры могут быть разной длины. В таком случае, итерирование по zip должно останавливаться как только будет исчерпан контейнер с наименьшим числом элементов.
Послабление: считаем, что у каждого контейнера в zip есть метод size(),
тогда правильный end() можно построить в любой момент (возможно, дорого по времени)
NB: объект zip не должен получать контейнеры во владение. Ответственность пользователя — обеспечить время жизни контейнеров таким образом, чтобы zip имел валидные данные для итерирования, а также сохранение валидного состояния итераторов при итерировании с помощью zip
-
В базовой части, для определения своего итератора стоит явно определить пропсы из
std::iterator_traits<MyIterator>— чтобы класс числился итератором -
В коде присутствует сырая заготовка классов — в каждом из них для описания внутреннего стейта достаточно использовать
std::tupleнад определенными элементами:-
Zip— знает про контейнеры; умеет порождатьbegin()иend() -
ZipIterator— знает про положение текущих итераторов, умеется сдвигаться и разыменовываться -
ZipTuple— знает про конкретные элементы контейнеров, умеет получать к ним доступ
-
-
Для удобной работы с кортежами пользуйтесь std::apply
-
Поддержите возможность передачи констаных контейнеров в
zip-
на вход zip могут прийти и константые и неконстантые контейнеры одновременно
-
-
При условии, что все контейнеры поддерживают двунаправленное итерирование, поддержите:
-
operator--(pre/post) у итераторов -
новый класс
zip::reverse_iterator— чтобы итерироваться в обратном порядке
-
Поддержите возможность использовать анпакинг специального вида:
std::vector<size_t> x{1,2,3};
std::string str = "hello";
for (auto&& [i, c] : zip(x, str)) {
std::cout << i << "; " << c << std::endl;
}Что для этого нужно — см. в binding a tuple-like type