- PVSM.RU - https://www.pvsm.ru -
Освоение стандарта C++11 — процесс, который не может происходить скачкообразно. Изучение новой языковой конструкции требует не только заучивания синтаксиса, но и осмысления её предназначения и типичных способов применения. Важным подспорьем в обучении является похорошевшая STL, которая зачастую может открыть глаза на существование весьма интересных и нужных возможностей. А уж зная, что какая-то вещь возможна и реализована в STL, докопаться до способа реализации нетрудно.
Об одном из любопытных примеров, связанном с обновлённым и улучшенным классом pair, и пойдёт речь в статье.
Новый стандарт добавил такой, казалось бы, простой вещи, как pair [1], удобства и универсальности. Если раньше к типам, входящим в состав пары, предъявлялись достаточно суровые требования, то сейчас слепить в пару можно практически что угодно. В частности, снято ограничение на конструирование таких типов. Теперь необязательно применять операции копирования или даже перемещения, возможно создание пары непосредственным конструированием членов (такая операция называется emplace, «размещение», и в C++11 поддерживается контейнерами STL), с применением нетривиальных конструкторов.
… А вот тут, как говорится, подробнее. Каким образом мы можем вызвать конструктор pair и передать ему два набора аргументов, да так, чтобы он понял, какие аргументы отдать какому конструктору? Среди привычных конструкторов, связанных с копированием или перемещением членов или целикового pair, видим такое:
template< class... Args1, class... Args2 >
pair( std::piecewise_construct_t,
std::tuple<Args1...> first_args,
std::tuple<Args2...> second_args );
piecewise_construct_t — просто пустой тип, который поможет нам сигнализировать, что мы хотим именно создать pair по кусочкам, передав аргументы конструкторам first и second. В этом нам поможет константа такого типа под названием piecewise_construct. Ну а дальше мы указываем два набора аргументов, упаковав их в кортежи (tuple [2], в их создании поможет функция make_tuple [3]). Для тех читателей, которые забыли или не в курсе, что это такое, напомню: кортеж — собрание произвольного количества значений произвольного типа. В C++ с его строгим контролем типов кортежи реализованы при помощи шаблонов с переменным количеством аргументов (variadic template [4]).
Что же, вроде бы проблема удачно решена: в качестве «упаковок» аргументов для конструкторов first и second выступают кортежи. На этом этапе у программиста, знакомящегося с новинками стандарта, может возникнуть вопрос: «Кстати, а как распаковываются данные из кортежей?» Документация даёт нам единственный способ: функция get [5], которой в качестве шаблонного параметра указывают индекс элемента в кортеже.
Каким же образом наши аргументы попадут в конструктор? Извлекать данные из кортежа по одному несложно. Несложно извлекать их рекурсивно. Но как уместить все значения в вызове функции (в данном случае — конструктора)?
Здесь пригодится распаковка variadic-шаблонов. Однако распаковывать надо не список типов tuple, а список индексов. Который сначала надо ещё изготовить.
Начнём с основного: сделаем добавление нового индекса к уже существующему списку. Очевидно, что если нумерация начинается с 0, то новый индекс будет равен размеру входного списка. Нам понадобится структура, которую мы параметризуем списком индексов:
template<size_t ... Indices> struct PackIndices {
// Здесь мы добавим к Indices новое число, равное sizeof ... (Indices)
};
Результат, представляющий из себя список целочисленных констант времени компиляции и одновременно — аргументы шаблона, нельзя хранить сам по себе, но можно хранить тип, параметризованный этими аргументами. И у нас как раз уже есть подходящий кандидат на роль такого типа:
template<size_t ... Indices> struct PackIndices {
typedef PackIndices<Indices... , sizeof ... (Indices)> next;
};
Теперь сделаем рекурсивный генератор, создающий список индексов длиной N. Делается это простым добавлением последнего индекса к списку длиной N-1:
template<size_t N> struct CreatePackIndices {
typedef typename CreatePackIndices<N-1>::type::next type;
};
… и остановим рекурсию:
template<> struct CreatePackIndices<0> {
typedef PackIndices<> type;
};
Заполучив способ создания списка индексов, займёмся распаковкой кортежа в параметры конструктора. Для простоты рассмотрим сначала конструирование только одного объекта, first.
С использованием кортежа args и списка индексов Indices распаковка должна выглядеть в своей основе так:
first(std::get<Indices>(args)...)
Чтобы получить доступ к Indices, надо, чтобы контекст, в котором мы производим распаковку (то есть, конструктор pair), был этим списком параметризован. Это означает, что нам понадобится создать второй конструктор-шаблон со всеми нужными параметрами. Здесь кстати придётся ещё одна новинка C++11, делегирование конструкторов, которое позволяет вызывать альтернативный конструктор в списке инициализации. А поскольку мы всё равно производим вызовы функций, то воспользуемся автоматическим выводом типа аргументов: передадим вспомогательному конструктору анонимный объект PackIndices. В результате мы получаем такой одноногий pair:
template<typename T> class pair {
// Конструктор, распаковывающий кортеж
template<typename ... ArgTypes, size_t ... Indices>
pair(std::tuple<ArgTypes...>& first_args, PackIndices<Indices...>):
first(std::get<Indices>(first_args)...)
{}
public:
// Конструктор, доступный пользователю
template<typename ... ArgTypes>
pair(std::piecewise_construct_t, const std::tuple<ArgTypes...>& first_args):
pair(first_args, typename CreatePackIndices<sizeof ... (ArgTypes)>::type())
{}
private:
T first;
};
Здесь самое время вспомнить про perfect forwarding [6] — механизм, необходимый для корректной передачи аргументов во вложенные вызовы без изменения их типов. В обновлённом STL предусмотрена функция forward [7], которую придётся применить к каждому аргументу и к тому же параметризовать типом аргумента. К счастью, создатели нового стандарта предусмотрели такую хитрую штуку, как одновременная распаковка нескольких наборов аргументов. Поскольку ArgTypes и Indices заведомо имеют одинаковую длину, можно смело добавить вызов forward в паттерн распаковки:
template<typename ... ArgTypes, size_t ... Indices>
pair(std::tuple<ArgTypes...>& first_args, PackIndices<Indices...>):
first(std::forward<ArgTypes>(std::get<Indices>(first_args))...)
{}
После того, как пройден весь путь от значений в make_tuple до параметров конструктора, поставим pair на обе ноги:
template<typename T1, typename T2> class pair {
// Конструктор, распаковывающий кортеж
template<typename ... ArgTypes1, size_t ... Indices1, typename ... ArgTypes2, size_t ... Indices2>
pair(std::tuple<ArgTypes1...>& first_args, std::tuple<ArgTypes2...>& second_args,
PackIndices<Indices1...>, PackIndices<Indices2...>):
first(std::forward<ArgTypes1>(std::get<Indices1>(first_args))...),
second(std::forward<ArgTypes2>(std::get<Indices2>(second_args))...)
{}
public:
// Конструктор, доступный пользователю
template<typename ... ArgTypes1, typename ... ArgTypes2>
pair(std::piecewise_construct_t, std::tuple<ArgTypes1...> first_args, std::tuple<ArgTypes2...> second_args):
pair(first_args, second_args,
typename CreatePackIndices<sizeof ... (ArgTypes1)>::type(),
typename CreatePackIndices<sizeof ... (ArgTypes2)>::type())
{}
private:
T1 first;
T2 second;
};
Разумеется, этот приём полезен не только для создания кустарно-велосипедного pair. Так, программисту, имеющему дело со стековыми виртуальными машинами [8], наверняка придут на ум врапперы для функций. Несомненно, найдутся применения и в других областях.
Автор: OldFisher
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-3/36857
Ссылки в тексте:
[1] pair: http://en.cppreference.com/w/cpp/utility/pair
[2] tuple: http://en.cppreference.com/w/cpp/utility/tuple
[3] make_tuple: http://en.cppreference.com/w/cpp/utility/tuple/make_tuple
[4] variadic template: http://habrahabr.ru/post/101430/
[5] get: http://en.cppreference.com/w/cpp/utility/tuple/get
[6] perfect forwarding: http://thbecker.net/articles/rvalue_references/section_07.html
[7] forward: http://en.cppreference.com/w/cpp/utility/forward
[8] стековыми виртуальными машинами: http://www.lua.org/
[9] Источник: http://habrahabr.ru/post/183830/
Нажмите здесь для печати.