[C++] Доступ к элементам std::tuple во время исполнения программы
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
При тестировании разрабатываемой библиотеки математических алгоритмов для автономного вождения нашей команде приходилось достаточно много манипулировать с кортежами (std::tuple): итерироваться по каждому элементу кортежа или в произвольном порядке, выбирать элементы, чьи индексы и/или значения удовлетворяют определенному условию, и т.д. Написание для каждого случая отдельной рекурсивной функции прохода по кортежу, во-первых, требует знания основ метапрограммирования и шаблонной магии, а во-вторых, отнимает существенное количество времени разработчика.Мне в голову пришла идея: а что если получать доступ к элементам по индексу, не известному на этапе компиляции?Теоретические предпосылкиКак известно, кортеж представляет из себя гетерогенный список, и в общем случае возвращаемое значение вышеупомянутой функции std::get может быть разным и зависит от переданного ей шаблонного параметра – индекса элемента в кортеже. Так, для типа std::tuple std::get<0> вернет int (на самом деле ссылку на int), а std::get<1> – double. Напишем объявление функции, которую мы хотим реализовать:
template <class TupleT>
ReturnT get(TupleT && tuple, std::size_t index);
Шаблонный параметр TupleT – это тип передаваемого в функцию кортежа, а ReturnT – возвращаемое значение, которое нам необходимо определить. Данная функция возвращает элемент кортежа под номером index, значение которого мы не знаем во время компиляции, а следовательно, и не знаем тип элемента кортежа. Значит, нам нужен какой-то тип, который удовлетворяет всем типам, содержащимся в интересующем нас кортеже. Возвращаемый типВ 17 стандарте есть класс std::any, который может в себе хранить объект любого типа, однако хотелось бы избежать динамического выделения памяти для простого доступа элемента. Второй возможный тип – это шаблонный класс std::variant, параметры которого мы сейчас обсудим.Но вначале надо определиться, что делать в случае, если нам передали невалидное значение индекса. Первый вариант, который приходит в голову, – это бросать исключение, например, объект типа std::out_of_range. Второй вариант – это возвращать какой-нибудь специальный тип, сигнализирующий невалидность переданного типа. Первый вариант показался мне более предпочтительным, потому что возникновение исключительной ситуации в данном случае – это явно вина программиста, и ее, по идее, возникать не должно.Теперь перейдем к возвращаемому значению. Первая мысль, которая пришла мне в голову – это возвращать std::variant с такими же шаблонными параметрами, как и подаваемый на вход кортеж. Напишем метафункцию на подобие type_traits стандартной библиотеки для этого:
template <class T>
struct ToVariant;
template <class T>
using ToVariantT = typename ToVariant<T>::type;
template <class... T>
struct ToVariant<std::tuple<T...>>
{
using type = std::variant<T...>;
};
Эта метафункция определена только для типа std::tuple. Отлично, теперь у нас есть возвращаемый тип. Однако при каждом вызове нашей функции get происходит копирование имеющегося объекта, что в общем случае может быть достаточно дорогостоящей операцией.Для решения этой проблемы мне показалось оптимальным использовать класс std::reference_wrapper из стандартной библиотеки, чтобы в нашем «варианте» хранить только ссылки на существующие объекты. Метафункция с учетом константности кортежа запишется так:
template <class T>
struct ToVariantRef;
template <class T>
using ToVariantRefT = typename ToVariantRef<T>::type;
template <class... T>
struct ToVariantRef<const std::tuple<T...>>
{
using type = std::variant<std::reference_wrapper<std::add_const_t<T>>...>;
};
template <class... T>
struct ToVariantRef<std::tuple<T...>>
{
using type = std::variant<std::reference_wrapper<T>...>;
};
Реализация функции get Теперь я хочу привести реализации функции get в том порядке, в котором они приходили мне в голову. Первой идеей было использовать std::map, где ключам соответствовал бы индекс элемента, а значениям – непосредственно значения элементов кортежа. Реализация приведена ниже:
namespace detail
{
template <class TupleTRef, std::size_t... Idx,
class ReturnT = ToVariantRefT<std::remove_reference_t<TupleTRef>>>
std::map<std::size_t, ReturnT> buildVariantMap(TupleTRef&& tuple,
std::index_sequence<Idx...>)
{
std::map<std::size_t, ReturnT> variantMap;
(variantMap.emplace(Idx, ReturnT{
std::in_place_index_t<Idx>{},
std::ref( std::get<Idx>( std::forward<TupleTRef>( tuple ) ) ) }), ...);
return variantMap;
}
} // namespace detail
template <class TupleTRef>
auto variantMapGet(TupleTRef && tuple, std::size_t index)
-> ToVariantRefT<std::remove_reference_t<TupleTRef>>
{
using TupleT = std::remove_reference_t<TupleTRef>;
auto variantMap = detail::buildVariantMap(
std::forward<TupleTRef>(tuple),
std::make_index_sequence<std::tuple_size_v<TupleT>>{});
return variantMap.at(index);
}
Мы используем std::make_index_sequence, чтобы из размера кортежа N получить последовательность 0...N-1, а в функции buildVariantMap мы используем fold expressions, чтобы заполнить нашу «мапу» значениями. Минусы такого подхода очевидны – из-за динамического выделения памяти такой подход будет работать очень медленно.Еще одной идеей для улучшения было использование std::array вместо std::map. Я решил не приводить реализацию, т.к. она схожа с реализацией, использующей std::map. В таком случае все наши данные будут лежать на стеке и данный подход намного быстрее использования std::map. Однако создавать массив на N элементов для доступа всего к одному из них все равно достаточно расточительно.Следующей моей мыслью было использовать линейный поиск индекса элемента, реализация:
namespace detail
{
template <std::size_t Idx, class TupleTRef>
auto linearGetImpl(TupleTRef&& tuple, std::size_t index)
-> ToVariantRefT<std::remove_reference_t<TupleTRef>>
{
using TupleT = std::remove_reference_t<TupleTRef>;
using ReturnT = ToVariantRefT<std::remove_reference_t<TupleTRef>>;
if constexpr (Idx < std::tuple_size_v<TupleT>)
{
if (Idx == index)
{
return ReturnT{ std::in_place_index_t<Idx>{},
std::ref(std::get<Idx>(std::forward<TupleTRef>(tuple))) };
}
return linearGetImpl<Idx + 1>(std::forward<TupleTRef>(tuple), index);
}
else
{
throw std::out_of_range("Error! Tuple index is out of range!\n");
}
}
} // namespace detail
template <class TupleTRef>
auto linearGet(TupleTRef&& tuple, std::size_t index)
-> ToVariantRefT<std::remove_reference_t<TupleTRef>>
{
using TupleT = std::remove_reference_t<TupleTRef>;
if (index >= std::tuple_size_v<TupleT>)
{
throw std::out_of_range("Error! Tuple index is out of range!\n");
}
return detail::linearGetImpl<0>(std::forward<TupleTRef>(tuple), index);
}
Мы просто проверяем, равен ли index текущему значению шаблонного параметра Idx, и если нет, то вызываем функцию detail::linearGetImpl с параметром Idx + 1. Такой подход оказывается существенно быстрее использования std::array, но в таком случае у нас доступ к элементу имеет сложность O(N), что все равно недостаточно хорошо.Затем я решил улучшить эту реализацию, используя бинарный поиск:
namespace detail
{
template <std::size_t FirstIdx, std::size_t LastIdx, class TupleTRef>
auto binaryGetImpl(TupleTRef && tuple, std::size_t index)
-> ToVariantRefT<std::remove_reference_t<TupleTRef>>
{
using TupleT = std::remove_reference_t<TupleTRef>;
using ReturnT = ToVariantRefT<std::remove_reference_t<TupleTRef>>;
constexpr auto MiddleIdx = (FirstIdx + LastIdx) / 2;
if constexpr (FirstIdx == LastIdx)
{
return ReturnT{ std::in_place_index_t<MiddleIdx>{},
std::ref(std::get<MiddleIdx>(std::forward<TupleTRef>(tuple))) };
}
else
{
if constexpr (MiddleIdx > 0)
{
if (MiddleIdx > index)
{
return binaryGetImpl<FirstIdx, MiddleIdx - 1>(tuple, index);
}
}
if (MiddleIdx < index)
{
return binaryGetImpl<MiddleIdx + 1, LastIdx>(tuple, index);
}
return ReturnT{ std::in_place_index_t<MiddleIdx>{},
std::ref(std::get<MiddleIdx>(std::forward<TupleTRef>(tuple))) };
}
}
} // namespace detail
template <class TupleTRef>
auto binaryGet(TupleTRef && tuple, std::size_t index)
-> ToVariantRefT<std::remove_reference_t<TupleTRef>>
{
using TupleT = std::remove_reference_t<TupleTRef>;
if (index >= std::tuple_size_v<TupleT>)
{
throw std::out_of_range("Error! Tuple index is out of range!\n");
}
return detail::binaryGetImpl<0, std::tuple_size_v<TupleT> - 1>(
std::forward<TupleTRef>(tuple), index);
}
Суть бинарного поиска заключается в том, что мы делим исходный отрезок 0...N-1 каждый раз пополам и идем в ту часть, где находится искомый индекс index. Из-за использования двух шаблонных параметров std::size_t такая реализация, однако, может увеличить объем сгенерированного кода и, как следствие, бинарника.Наконец, можно получить O(1) реализацию при условии, что сложность оператора switch не зависит от N. Реализация выглядит криво, но имеет место быть:
namespace detail
{
template <class TupleTRef>
auto switchCaseGetImpl(TupleTRef&& tuple, std::size_t index)
-> ToVariantRefT<std::remove_reference_t<TupleTRef>>
{
using TupleT = std::remove_reference_t<TupleTRef>;
using ReturnT = ToVariantRefT<std::remove_reference_t<TupleTRef>>;
static_assert(std::tuple_size_v<TupleT> < 100);
#define CASE_N(I) \
case (I): \
if constexpr ((I) < std::tuple_size_v<TupleT>) \
{ \
return ReturnT{ std::in_place_index_t<I>{}, \
std::ref(std::get<I>(std::forward<TupleTRef>(tuple))) }; \
} \
else \
{ \
break; \
}
switch (index)
{
CASE_N(0);
CASE_N(1);
...
CASE_N(98);
CASE_N(99);
}
#undef CASE_N
}
} // namespace detail
template <class TupleTRef>
auto switchCaseGet(TupleTRef&& tuple, std::size_t index)
-> ToVariantRefT<std::remove_reference_t<TupleTRef>>
{
using TupleT = std::remove_reference_t<TupleTRef>;
if (index >= std::tuple_size_v<TupleT>)
{
throw std::out_of_range("Error! Tuple index is out of range!\n");
}
return detail::switchCaseGetImpl(std::forward<TupleTRef>(tuple), index);
}
Данная функция работает только с кортежами размера меньше 100 (выглядит как разумное допущение). А вместо ... в функции detail::switchCaseGetImpl идет набор CASE_N от 2 до 97. Для получения более красивого кода можно воспользоваться макросом из буста: BOOST_PP_REPEAT.БенчмаркиДля бенчмарков использовалась библиотека Google. Мы взяли std::tuple длиной 40, каждый тип которого – это int. Сложность каждой операции была порядка сложности нахождения целочисленного остатка от деления. Был выбран компилятор MSVC с флагом оптимизации O2. Результаты:BenchmarkTimeCPUIterationsBmSwitchCaseGet195 ns195 ns3733333BmBinaryGet258 ns257 ns2488889BmLinearGet492 ns500 ns1000000BmVariantMapGet169057 ns168795 ns4073BmVariantArrayGet1571 ns1573 ns407273BmCompileTimeGet124 ns126 ns6400000Из приведенных результатов видно, что switch-case реализация самая быстрая из вышеприведенных, а разница между std::get и нашей реализацией составляет приблизительно 50%. Такая разница, по-видимому, обусловлена лучшей способностью компилятора оптимизировать код в случае доступа к элементам во время компиляции, а также созданием объекта std::ref на каждый вызов нашей функции get. Однако, если выполнять какие-то нетривиальные действия с элементами кортежа, то разница, скорее всего, окажется незначительной. Разница в 70 ns – это разница для прохождения по всем элементам кортежа, то есть разница для доступа к одному элементу составляет порядка 2 ns.ЗаключениеВ итоге мы разработали функциональность, которая позволяет итерироваться по элементам кортежа в произвольном порядке и не писать отдельную функцию для каждого случая. Это существенно экономит время разработчика – надеюсь, сэкономит время и вам. В заключении хочу привести код того, как можно пользоваться разработанной функцией.Допустим, мы хотим вывести все элементы кортежа в поток std::cout . Код будет выглядит следующим образом:
using TupleT = std::tuple<int, double, std::string>;
int main()
{
const TupleT tuple{ 5, 3.0, "str"};
for (std::size_t idx{}; idx < std::tuple_size_v<TupleT>; ++idx)
{
std::visit([](auto && tupleValue) { std::cout << tupleValue.get() << " "; },
rttg::get(tuple, idx));
}
}
Для того, чтобы в лямбде не писать каждый раз .get(), мы будем использовать обертку над функцией std::visit:
template <class CallableT, class... VariantTs>
auto visit(CallableT && callable, VariantTs && ... variants)
{
return std::visit(
[&callable](auto && value)
{
return std::invoke(std::forward<CallableT>(callable), value.get());
},
std::forward<VariantTs>(variants)...);
}
Таким образом, финальный вариант можно записать без лишних .get():
using TupleT = std::tuple<int, double, std::string>;
int main()
{
const TupleT tuple{ 5, 3.0, "str"};
for (std::size_t idx{}; idx < std::tuple_size_v<TupleT>; ++idx)
{
rttg::visit([](auto && tupleValue) { std::cout << tupleValue << " "; },
rttg::get(tuple, idx));
}
}
Для перегрузки работы с различными типами можно использовать overload trick:
/**
* Agregator of callables with operator()
*/
template <class... CallableTs>
struct Overloader : CallableTs...
{
using CallableTs::operator()...;
};
/**
* Class template argument deduction guide
*/
template <class... CallableTs> Overloader(CallableTs ...) -> Overloader<CallableTs...>;
В таком случае пример можно переписать в виде:
using TupleT = std::tuple<int, double, std::string>;
int main()
{
const TupleT tuple{ 5, 3.0, "str"};
for (std::size_t idx{}; idx < std::tuple_size_v<TupleT>; ++idx)
{
rttg::visit(
rttg::Overloader
{
[](auto arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string & arg) { std::cout << std::quoted(arg) << ' '; }
},
rttg::get(tuple, idx));
}
}
Для такого простого примера, конечно, можно было воспользоваться функцией на подобие for_each. Однако наша функция позволяет итерироваться в произвольном порядке, а не обязательно в возрастающем/убывающем:
using TupleT = std::tuple<std::string, int, std::string, int, std::string, int>;
std::vector<std::size_t> getIterableIndices()
{
// some non-trivial logic here
return std::vector<std::size_t>{ 2U, 3U, 0U, 1U };
}
int main()
{
const TupleT tuple{ "str1", 1, "str2", 2, "str3", 3 };
for (const auto idx : getIterableIndices())
{
rttg::visit([](auto && tupleValue) { std::cout << tupleValue << " "; },
rttg::get(tuple, idx));
}
}
Ссылка на репозиторий: https://github.com/AlexKiryushkin/rttg.P.S. В итоге было решено оставить бинарную версию функции get из-за относительно небольшой разницы в производительности и более удобоваримой реализации по сравнению со switch-case.P.S.S. Для кортежей размера < 7 линейный поиск работает быстрее бинарного и приблизительно наравне с реализацией switch-case.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, C++, Промышленное программирование, Программирование микроконтроллеров] Маленькие хитрости для STM32
- [Программирование, C++, Алгоритмы] Ленивые операции над множествами в C++
- [Программирование, C++, Алгоритмы] Ленивые итераторы и диапазоны в C++
- [C++, C] Макросы в С и С++
- [Программирование, C++, C, Разработка под Linux] Неблокирующие паттерны: атомарные операции и частичные барьеры памяти (перевод)
- [C++, C, Программирование микроконтроллеров, Компьютерное железо] Размещение кучи FreeRTOS в разделе CCMRAM для STM32
- [Программирование, C++] Динамический полиморфизм с использованием std::variant и std::visit (перевод)
- [Программирование, C++, C, Разработка под Linux] Введение в неблокирующие алгоритмы (перевод)
- [Тестирование IT-систем, Тестирование веб-сервисов, Учебный процесс в IT, Карьера в IT-индустрии] ISTQB. Как проходит сдача экзамена онлайн
- [C++, Assembler, C] Программа: исполняемая, управляющая или выполнимая или?
Теги для поиска: #_c++, #_tuple, #_templates, #_metaprogramming, #_blog_kompanii_auriga (
Блог компании Auriga
), #_c++
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:41
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
При тестировании разрабатываемой библиотеки математических алгоритмов для автономного вождения нашей команде приходилось достаточно много манипулировать с кортежами (std::tuple): итерироваться по каждому элементу кортежа или в произвольном порядке, выбирать элементы, чьи индексы и/или значения удовлетворяют определенному условию, и т.д. Написание для каждого случая отдельной рекурсивной функции прохода по кортежу, во-первых, требует знания основ метапрограммирования и шаблонной магии, а во-вторых, отнимает существенное количество времени разработчика.Мне в голову пришла идея: а что если получать доступ к элементам по индексу, не известному на этапе компиляции?Теоретические предпосылкиКак известно, кортеж представляет из себя гетерогенный список, и в общем случае возвращаемое значение вышеупомянутой функции std::get может быть разным и зависит от переданного ей шаблонного параметра – индекса элемента в кортеже. Так, для типа std::tuple std::get<0> вернет int (на самом деле ссылку на int), а std::get<1> – double. Напишем объявление функции, которую мы хотим реализовать: template <class TupleT>
ReturnT get(TupleT && tuple, std::size_t index); template <class T>
struct ToVariant; template <class T> using ToVariantT = typename ToVariant<T>::type; template <class... T> struct ToVariant<std::tuple<T...>> { using type = std::variant<T...>; }; template <class T>
struct ToVariantRef; template <class T> using ToVariantRefT = typename ToVariantRef<T>::type; template <class... T> struct ToVariantRef<const std::tuple<T...>> { using type = std::variant<std::reference_wrapper<std::add_const_t<T>>...>; }; template <class... T> struct ToVariantRef<std::tuple<T...>> { using type = std::variant<std::reference_wrapper<T>...>; }; namespace detail
{ template <class TupleTRef, std::size_t... Idx, class ReturnT = ToVariantRefT<std::remove_reference_t<TupleTRef>>> std::map<std::size_t, ReturnT> buildVariantMap(TupleTRef&& tuple, std::index_sequence<Idx...>) { std::map<std::size_t, ReturnT> variantMap; (variantMap.emplace(Idx, ReturnT{ std::in_place_index_t<Idx>{}, std::ref( std::get<Idx>( std::forward<TupleTRef>( tuple ) ) ) }), ...); return variantMap; } } // namespace detail template <class TupleTRef> auto variantMapGet(TupleTRef && tuple, std::size_t index) -> ToVariantRefT<std::remove_reference_t<TupleTRef>> { using TupleT = std::remove_reference_t<TupleTRef>; auto variantMap = detail::buildVariantMap( std::forward<TupleTRef>(tuple), std::make_index_sequence<std::tuple_size_v<TupleT>>{}); return variantMap.at(index); } namespace detail
{ template <std::size_t Idx, class TupleTRef> auto linearGetImpl(TupleTRef&& tuple, std::size_t index) -> ToVariantRefT<std::remove_reference_t<TupleTRef>> { using TupleT = std::remove_reference_t<TupleTRef>; using ReturnT = ToVariantRefT<std::remove_reference_t<TupleTRef>>; if constexpr (Idx < std::tuple_size_v<TupleT>) { if (Idx == index) { return ReturnT{ std::in_place_index_t<Idx>{}, std::ref(std::get<Idx>(std::forward<TupleTRef>(tuple))) }; } return linearGetImpl<Idx + 1>(std::forward<TupleTRef>(tuple), index); } else { throw std::out_of_range("Error! Tuple index is out of range!\n"); } } } // namespace detail template <class TupleTRef> auto linearGet(TupleTRef&& tuple, std::size_t index) -> ToVariantRefT<std::remove_reference_t<TupleTRef>> { using TupleT = std::remove_reference_t<TupleTRef>; if (index >= std::tuple_size_v<TupleT>) { throw std::out_of_range("Error! Tuple index is out of range!\n"); } return detail::linearGetImpl<0>(std::forward<TupleTRef>(tuple), index); } namespace detail
{ template <std::size_t FirstIdx, std::size_t LastIdx, class TupleTRef> auto binaryGetImpl(TupleTRef && tuple, std::size_t index) -> ToVariantRefT<std::remove_reference_t<TupleTRef>> { using TupleT = std::remove_reference_t<TupleTRef>; using ReturnT = ToVariantRefT<std::remove_reference_t<TupleTRef>>; constexpr auto MiddleIdx = (FirstIdx + LastIdx) / 2; if constexpr (FirstIdx == LastIdx) { return ReturnT{ std::in_place_index_t<MiddleIdx>{}, std::ref(std::get<MiddleIdx>(std::forward<TupleTRef>(tuple))) }; } else { if constexpr (MiddleIdx > 0) { if (MiddleIdx > index) { return binaryGetImpl<FirstIdx, MiddleIdx - 1>(tuple, index); } } if (MiddleIdx < index) { return binaryGetImpl<MiddleIdx + 1, LastIdx>(tuple, index); } return ReturnT{ std::in_place_index_t<MiddleIdx>{}, std::ref(std::get<MiddleIdx>(std::forward<TupleTRef>(tuple))) }; } } } // namespace detail template <class TupleTRef> auto binaryGet(TupleTRef && tuple, std::size_t index) -> ToVariantRefT<std::remove_reference_t<TupleTRef>> { using TupleT = std::remove_reference_t<TupleTRef>; if (index >= std::tuple_size_v<TupleT>) { throw std::out_of_range("Error! Tuple index is out of range!\n"); } return detail::binaryGetImpl<0, std::tuple_size_v<TupleT> - 1>( std::forward<TupleTRef>(tuple), index); } namespace detail
{ template <class TupleTRef> auto switchCaseGetImpl(TupleTRef&& tuple, std::size_t index) -> ToVariantRefT<std::remove_reference_t<TupleTRef>> { using TupleT = std::remove_reference_t<TupleTRef>; using ReturnT = ToVariantRefT<std::remove_reference_t<TupleTRef>>; static_assert(std::tuple_size_v<TupleT> < 100); #define CASE_N(I) \ case (I): \ if constexpr ((I) < std::tuple_size_v<TupleT>) \ { \ return ReturnT{ std::in_place_index_t<I>{}, \ std::ref(std::get<I>(std::forward<TupleTRef>(tuple))) }; \ } \ else \ { \ break; \ } switch (index) { CASE_N(0); CASE_N(1); ... CASE_N(98); CASE_N(99); } #undef CASE_N } } // namespace detail template <class TupleTRef> auto switchCaseGet(TupleTRef&& tuple, std::size_t index) -> ToVariantRefT<std::remove_reference_t<TupleTRef>> { using TupleT = std::remove_reference_t<TupleTRef>; if (index >= std::tuple_size_v<TupleT>) { throw std::out_of_range("Error! Tuple index is out of range!\n"); } return detail::switchCaseGetImpl(std::forward<TupleTRef>(tuple), index); } using TupleT = std::tuple<int, double, std::string>;
int main() { const TupleT tuple{ 5, 3.0, "str"}; for (std::size_t idx{}; idx < std::tuple_size_v<TupleT>; ++idx) { std::visit([](auto && tupleValue) { std::cout << tupleValue.get() << " "; }, rttg::get(tuple, idx)); } } template <class CallableT, class... VariantTs>
auto visit(CallableT && callable, VariantTs && ... variants) { return std::visit( [&callable](auto && value) { return std::invoke(std::forward<CallableT>(callable), value.get()); }, std::forward<VariantTs>(variants)...); } using TupleT = std::tuple<int, double, std::string>;
int main() { const TupleT tuple{ 5, 3.0, "str"}; for (std::size_t idx{}; idx < std::tuple_size_v<TupleT>; ++idx) { rttg::visit([](auto && tupleValue) { std::cout << tupleValue << " "; }, rttg::get(tuple, idx)); } } /**
* Agregator of callables with operator() */ template <class... CallableTs> struct Overloader : CallableTs... { using CallableTs::operator()...; }; /** * Class template argument deduction guide */ template <class... CallableTs> Overloader(CallableTs ...) -> Overloader<CallableTs...>; using TupleT = std::tuple<int, double, std::string>;
int main() { const TupleT tuple{ 5, 3.0, "str"}; for (std::size_t idx{}; idx < std::tuple_size_v<TupleT>; ++idx) { rttg::visit( rttg::Overloader { [](auto arg) { std::cout << arg << ' '; }, [](double arg) { std::cout << std::fixed << arg << ' '; }, [](const std::string & arg) { std::cout << std::quoted(arg) << ' '; } }, rttg::get(tuple, idx)); } } using TupleT = std::tuple<std::string, int, std::string, int, std::string, int>;
std::vector<std::size_t> getIterableIndices() { // some non-trivial logic here return std::vector<std::size_t>{ 2U, 3U, 0U, 1U }; } int main() { const TupleT tuple{ "str1", 1, "str2", 2, "str3", 3 }; for (const auto idx : getIterableIndices()) { rttg::visit([](auto && tupleValue) { std::cout << tupleValue << " "; }, rttg::get(tuple, idx)); } } =========== Источник: habr.com =========== Похожие новости:
Блог компании Auriga ), #_c++ |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:41
Часовой пояс: UTC + 5