寻求优化C++中std::apply可变参数带索引传递的indexed()实现方案
寻求优化C++中std::apply可变参数带索引传递的indexed()实现方案
我最近在琢磨怎么用std::apply的时候,能干净利落地给可变参数带上索引——就是把元组拆包传给可调用对象时,每个参数都能拿到自己的位置索引。
先给大家看一个不算完美但还算简洁的写法:
const auto animals = std::make_tuple("cow", "dog", "sheep"); std::apply([](const auto& ... str){ const auto print = [](const auto& str, size_t index){ std::cout << index << ": " << str << '\n'; }; size_t i = 0; (print(str, i++), ...); }, animals);
这个写法比用std::index_sequence加重载的方式清爽不少,因为不用在lambda外面写额外代码——毕竟C++的块作用域里不能定义模板,要是用index_sequence的话还得在外头搞辅助类。但它的问题也很明显:得手动维护一个可变状态i,不够优雅,我觉得索引应该是能隐式获取、不用用户手动管理的才对。
我心里理想的用法是类似JavaScript的解构(可惜C++不允许把结构化绑定当函数参数),大概是这样:
// 理想状态(C++目前没法直接实现) std::apply([](auto ... {value, index}){ // 直接用每个参数的index和value }, animals);
退而求其次,我自己实现了一个indexed()包装器,用法是这样的:
const auto animals = std::make_tuple("cow", "dog", "sheep"); std::apply(indexed([](auto ... indexedValue){ const auto print = [](const auto& indexedValue){ const auto &[index, value] = indexedValue; std::cout << index << ": " << value << '\n'; }; (print(indexedValue), ...); }), animals);
这个API比手动计数器或者写一堆模板 boilerplate 要好,只需要把可调用对象包进indexed()里就行,也不用修改STL的内容。但问题是我写的indexed()实现不够高效,生成的汇编指令比第一个手动计数器的版本多不少。
下面是我目前的indexed()实现,希望能得到优化建议:
#include <cstddef> #include <type_traits> #include <tuple> namespace detail { template <size_t I, typename T> struct _indexed { constexpr static size_t index = I; T value; constexpr _indexed(std::integral_constant<size_t, I>, T t) : value(t) {} template <size_t Elem> friend constexpr auto get(const detail::_indexed<I, T>& v) noexcept -> std::tuple_element_t<Elem, detail::_indexed<I, T>>{ if constexpr (Elem == 0) return I; if constexpr (Elem == 1) return v.value; } }; template <size_t I, typename T> _indexed(std::integral_constant<size_t, I>, T) -> _indexed<I, T>; template <typename CRTP> class _add_indices { public: template <typename ... Args> constexpr decltype(auto) operator()(Args &&... args) const noexcept { return (*this)(std::make_index_sequence<sizeof...(Args)>(), std::forward<Args>(args)...); } private: template <typename ... Args, size_t ... I> constexpr decltype(auto) operator()(std::index_sequence<I...>, Args ... args) const noexcept { return static_cast<const CRTP&>(*this).callable(_indexed(std::integral_constant<size_t, I>{}, std::forward<Args>(args))...); } }; } template <size_t I, typename T> struct std::tuple_size<detail::_indexed<I, T>> : std::integral_constant<size_t, 2> {}; template <size_t I, typename T> struct std::tuple_element<0, detail::_indexed<I, T>> { using type = size_t; }; template <size_t I, typename T> struct std::tuple_element<1, detail::_indexed<I, T>> { using type = T; }; template <typename Callable> constexpr auto indexed(Callable c) noexcept{ struct _c : detail::_add_indices<_c> { Callable callable; }; return _c{.callable = c}; }
这个实现的核心思路是给每个参数包装一个带索引的_indexed结构体,然后特化std::tuple_size和std::tuple_element让它支持结构化绑定。但编译出来的效率不如手动计数器的版本,想问问有没有办法优化这个实现,让它更高效,同时保持API的简洁性?
备注:内容来源于stack exchange,提问作者Sergey Kolesnik
相关产品推荐
相关产品推荐

