Преобразование кортежа в варианты задач

Мне нужно получить доступ к значению в 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 демо

Возможно, вы сможете адаптировать это

NathanOliver 06.07.2024 02:29

Успешно пройти clang и ICE из gcc и завершить работу из MSVC Демо. Далее нужно добиться успеха только MSVC ;-)

Jarod42 06.07.2024 03:21

@Jarod42 - какая ужасная история, все компиляторы сломаны, просто у них разные триггерные точки.

Gene 06.07.2024 04:28
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
134
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Работает, если вы используете

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 06.07.2024 03:00

@Gene Джин, я не уверен, это зависит от того, как сформулирован стандарт. Также может быть, что gcc принимает код, которого не должно быть.

LHLaurini 06.07.2024 03:03

И это еще одно возможное решение, которое принимается каждым компилятором. Преимущество заключается в том, что лямбда-функция является локальной внутри 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);
}

Демо

👍да, я пришел к такому же выводу

Gene 06.07.2024 05:57

Они не вызывают лямбду рекурсивно, get_n<0> вызывают другую лямбду get_n<1>

Artyer 06.07.2024 09:39

Нерекурсивная реализация с подробными типами будет такой:

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) последним.

Другие вопросы по теме