Мне нужно получить доступ к значению в std::tuple
по индексу во время выполнения. Для этой цели я написал функцию, которая возвращает std::variant
с теми же типами, которые инициализированы для указанного индекса. Реализация включает в себя рекурсивное создание экземпляров шаблона:
template<std::size_t N>
constexpr auto get_n = [](/*tuple-like*/auto&& t, std::size_t index)
{
using variant_type = decltype(std::apply([](auto&&... v){
return std::variant<std::monostate, std::remove_cvref_t<decltype(v)>...>{};
}, t));
constexpr auto size = std::tuple_size_v<std::remove_cvref_t<decltype(t)>>;
if constexpr(N > size)
return variant_type{};
else
{
if (N == index + 1)
return variant_type(std::in_place_index<N>, std::get<N - 1>(t));
else
return get_n<N + 1>(t, index);
}
};
constexpr auto tuple_to_variant(/*tuple-like*/auto&& t, std::size_t index)
{
return get_n<1>(std::forward<decltype(t)>(t), index);
}
Я использую компиляторы C++23. Проблема в том, что без ошибок компилируется только gcc. Компиляторы clang и MSVC переходят в бесконечный цикл создания экземпляров. Это ошибка в компиляторах? Как я могу исправить этот код, который компилируется во всех трех компиляторах?
Вот Compiler Explorer демо
Успешно пройти clang и ICE из gcc и завершить работу из MSVC Демо. Далее нужно добиться успеха только MSVC ;-)
@Jarod42 - какая ужасная история, все компиляторы сломаны, просто у них разные триггерные точки.
Работает, если вы используете
template<std::size_t N>
constexpr auto get_n(/*tuple-like*/auto&& t, std::size_t index)
вместо. У get_n
нет причин быть лямбдой.
Так что, в конце концов, это ошибка в компиляторах, нет причин, по которым отдельная функция могла бы работать, а лямбда - нет. Спасибо GCC за то, что все сделали правильно. P.S. причина в том, что это была лямбда, потому что изначально она была внутри функции tuple_to_variant, но потом мне пришлось сделать ее шаблонной и вынести наружу.
@Gene Джин, я не уверен, это зависит от того, как сформулирован стандарт. Также может быть, что gcc принимает код, которого не должно быть.
И это еще одно возможное решение, которое принимается каждым компилятором. Преимущество заключается в том, что лямбда-функция является локальной внутри tuple_to_variant
, поэтому она не создает никаких имен во внешней области.
inline auto tuple_to_variant(auto&& t, std::size_t index)
{
constexpr auto get_n = []<auto N>(this auto&& self, auto&& t, std::size_t index, std::index_sequence<N>)
{
using variant_type = decltype(std::apply([](auto&&... v){
return std::variant<std::monostate, std::remove_cvref_t<decltype(v)>...>{};
}, t));
constexpr auto size = std::tuple_size_v<std::remove_cvref_t<decltype(t)>>;
if constexpr(N > size)
return variant_type{};
else
{
if (N == index + 1)
return variant_type(std::in_place_index<N>, std::get<N - 1>(t));
else
return self(t, index, std::index_sequence<N + 1>{});
}
};
return get_n(std::forward<decltype(t)>(t), index, std::index_sequence<1>{});
}
Code Explorer демо
Поскольку вы вызываете лямбду рекурсивно, это возможно только с помощью явного параметра объекта C++23. Это должно работать:
constexpr auto get_n = []<std::size_t N>
(this auto self, /*tuple-like*/auto&& t, std::size_t index)
{
using variant_type = decltype(std::apply([](auto&&... v){
return std::variant<std::monostate, std::remove_cvref_t<decltype(v)>...>{};
}, t));
constexpr auto size = std::tuple_size_v<std::remove_cvref_t<decltype(t)>>;
if constexpr(N > size)
return variant_type{};
else
{
if (N == index + 1)
return variant_type(std::in_place_index<N>, std::get<N - 1>(t));
else
return self.template operator()<N + 1>(t, index);
}
};
constexpr auto tuple_to_variant(/*tuple-like*/auto&& t, std::size_t index)
{
return get_n.operator()<1>(std::forward<decltype(t)>(t), index);
}
👍да, я пришел к такому же выводу
Они не вызывают лямбду рекурсивно, get_n<0>
вызывают другую лямбду get_n<1>
Нерекурсивная реализация с подробными типами будет такой:
constexpr auto tuple_to_variant(std::size_t i, auto && t)
{
using ref_t = decltype(t);
using tuple = std::remove_cvref_t<ref_t>;
constexpr std::tuple_size<tuple> size;
return [&t]<std::size_t ... j>
(std::index_sequence<j ...>, auto i) {
using variant = std::variant
< std::monostate
, std::remove_cvref_t
< std::tuple_element_t<j, tuple> > ...>;
constexpr std::array dispatch {
+[](ref_t& lvalue_) {
return variant
{ std::in_place_index<1 + j>
, get<j>(static_cast<ref_t>(lvalue_)) };
}... ,
+[](ref_t&) { return variant{}; }
};// dispatch;
return dispatch[i](t);
}(make_index_sequence<size()>{}
, std::min(size(), i));
};// tuple_to_variant;
Мне нравится ставить активный/рабочий/выбирающий аргумент (i
) первым, а пассивный аргумент (t
) последним.
Возможно, вы сможете адаптировать это