По умолчанию создание std :: варианта из индекса - PullRequest
1 голос
/ 06 марта 2020

Какой самый простой способ по умолчанию создать std :: варианту из индекса нужного типа, когда индекс известен только во время выполнения? Другими словами, я хочу написать:

const auto indx = std::variant<types...>{someobject}.index();
//...somewhere later, indx having been passed around...
std::variant<types...> var = variant_from_index(indx);
///var is now set to contain a default constructed someobject

Обратите внимание, что indx нельзя сделать constexpr, поэтому std::in_place_index здесь не работает.

Проблема здесь, конечно, что поскольку неизвестно, какой конструктор из types... вызывать во время компиляции, каким-то образом, в основном, таблица всех возможных конструкторов (или, возможно, построенных по умолчанию вариантов для копирования) должна быть построена во время компиляции, а затем доступна во время выполнения , Какой-то шаблон magi c, очевидно, здесь на месте, но какой будет самый чистый путь?

Я пробовал следующее ( на coliru ), но последовательность индекса, кажется, выходит из строя (печать в конце дает 2 0 0), и я не понимаю, почему:

Редактировать: это работает, как исправлено ниже, у меня была неправильная инициализация массива constexpr. Итак, вопрос в том, есть ли более аккуратный способ сделать это?

#include <variant>
#include <iostream>

using var_t = std::variant<int, float, const char *>;

//For debug
template<class ...types>
struct WhichType;

template<class T, class U>
struct default_variants;
template<class...Params, std::size_t... I>
struct default_variants<std::variant<Params...>, std::index_sequence<I...>> {
    using variant_t = std::variant<Params...>;
    //Uncomment to see the index sequence
    //WhichType<std::index_sequence<I...>> idx{};
    constexpr static variant_t variants[sizeof...(Params)]{variant_t{std::in_place_index<I>}...};
    constexpr static std::size_t indices[sizeof...(Params)]{I...};
};
template<class T>
struct default_variants_builder;
template<class...Params>
struct default_variants_builder<std::variant<Params...>> {
    using indices = std::make_index_sequence<sizeof...(Params)>;
    using type = default_variants<std::variant<Params...>, indices>;
};


int main() {
    using builder_t = typename default_variants_builder<var_t>::type;
    var_t floatvar{1.2f};
    var_t variant2 = builder_t::variants[floatvar.index()];
    std::cout << "Contained " << floatvar.index() << "; Now contains " << variant2.index() << "\n";
}

Ответы [ 4 ]

6 голосов
/ 06 марта 2020

С Boost.Mp11 это в основном однострочный (как всегда):

template <typename V>
auto variant_from_index(size_t index) -> V
{
    return mp_with_index<mp_size<V>>(index,
        [](auto I){ return V(std::in_place_index<I>); });
}

Ваше описание проблемы точное - вам нужен способ повернуть среду выполнения индекс в индекс времени компиляции. mp_with_index сделает это за вас - вы дадите ему индекс времени выполнения и максимальный индекс времени компиляции (mp_size<V> здесь, который даст то же значение, что и std::variant_size_v<V>, если вы предпочтете это вместо этого), и он вызовет функцию, которую вы укажите правильную константу (здесь I имеет тип integral_constant<size_t, index>, за исключением того, что index является константным выражением).

4 голосов
/ 06 марта 2020

Как насчет этого?

template <class Variant, std::size_t I = 0>
Variant variant_from_index(std::size_t index) {
    if constexpr(I >= std::variant_size_v<Variant>)
        throw std::runtime_error{"Variant index " + std::to_string(I + index) + " out of bounds"};
    else
        return index == 0
            ? Variant{std::in_place_index<I>}
            : variant_from_index<Variant, I + 1>(index - 1);
}

Посмотреть в прямом эфире на Wandbox

2 голосов
/ 06 марта 2020

Не уверен, что это очень элегантно или нет, но я думаю, что это работает:

#include <variant>
#include <iostream>

template<typename V, std::size_t N = std::variant_size_v<V>>
struct variant_by_index {
    V make_default(std::size_t i) {
        if (i >= std::variant_size_v<V>) {
            throw std::invalid_argument("bad type index.");
        }
        constexpr size_t index = std::variant_size_v<V> - N;
        if (i == index) {
            return std::variant_alternative_t<index, V>();
        } else {
            return variant_by_index<V, N - 1>().make_default(i);
        }
    }
};

template<typename V>
struct variant_by_index<V, 0> {
    V make_default(std::size_t i) {
        throw std::bad_variant_access("bad type index.");
    }
};

using var_t = std::variant<int, float, const char *>;

int main() {
    variant_by_index<var_t> type_indexer;
    var_t my_var_0 = type_indexer.make_default(0);
    std::cout << "my_var_0 has int? " << std::holds_alternative<int>(my_var_0) <<  "\n";
    var_t my_var_1 = type_indexer.make_default(1);
    std::cout << "my_var_1 has float? " << std::holds_alternative<float>(my_var_1) <<  "\n";
    try {
        var_t my_var_1 = type_indexer.make_default(3);
    } catch(const std::bad_variant_access&) {
       std::cout << "Could not create with type 3.\n";
    }
    return 0;
}
1 голос
/ 06 марта 2020

Я полагаю, что (несколько) элегантный способ мог бы использовать более общую идиому для выбора числового c значения параметра шаблона во время выполнения, как обсуждалось в этом вопросе:

Идиома для моделирования число времени выполнения c параметры шаблона?

Функция foo будет std::get<std::size_t I> (или лямбда, которая фиксирует вариант и не принимает аргументов).

...