Получить индекс по типу в std :: option - PullRequest
0 голосов
/ 12 сентября 2018

Есть ли в стандартной библиотеке утилита для получения индекса заданного типа в std::variant?Или я должен сделать один для себя?То есть я хочу получить индекс B в std::variant<A, B, C> и получить это возвращение 1.

Для противоположной операции есть std::variant_alternative.Конечно, в списке std::variant может быть много одинаковых типов, поэтому эта операция не является биекцией, но для меня это не проблема (у меня может быть первое появление типа в списке или уникальных типов в std::variant список).

Ответы [ 5 ]

0 голосов
/ 13 сентября 2018

Мои два цента решения:

template <typename T, typename... Ts>
constexpr std::size_t variant_index_impl(std::variant<Ts...>**)
{
    std::size_t i = 0; ((!std::is_same_v<T, Ts> && ++i) && ...); return i;
}

template <typename T, typename V>
constexpr std::size_t variant_index_v = variant_index_impl<T>(static_cast<V**>(nullptr));

template <typename T, typename V, std::size_t... Is>
constexpr std::size_t variant_index_impl(std::index_sequence<Is...>)
{
    return ((std::is_same_v<T, std::variant_alternative_t<Is, V>> * Is) + ...);
}

template <typename T, typename V>
constexpr std::size_t variant_index_v = variant_index_impl<T, V>(std::make_index_sequence<std::variant_size_v<V>>{});

Если вы хотите получить серьезную ошибку при поиске, не содержащем тип или дубликат типа - вотстатические утверждения:

    constexpr auto occurrences = (std::is_same_v<T, Ts> + ...);
    static_assert(occurrences != 0, "The variant cannot have the type");
    static_assert(occurrences <= 1, "The variant has duplicates of the type");
0 голосов
/ 13 сентября 2018

Вы также можете сделать это с помощью выражения сгиба:

template <typename T, typename... Ts>
constexpr size_t get_index(std::variant<Ts...> const&) {
    size_t r = 0;
    auto test = [&](bool b){
        if (!b) ++r;
        return b;
    };
    (test(std::is_same_v<T,Ts>) || ...);
    return r;
}

Выражение сгиба останавливается при первом совпадении с типом, после чего мы прекращаем увеличивать r.Это работает даже с дубликатами типов.Если тип не найден, размер возвращается.Это может быть легко изменено на не return в этом случае, если это предпочтительнее, поскольку пропущенное return в функции constexpr неправильно сформировано.

Если вы не хотите использовать экземпляр variant, аргумент здесь может быть вместо tag<variant<Ts...>>.

0 голосов
/ 13 сентября 2018

Я нашел этот ответ для кортежа и немного изменил его:

template<typename VariantType, typename T, std::size_t index = 0>
constexpr std::size_t variant_index() {
    if constexpr (index == std::variant_size_v<VariantType>) {
        return index;
    } else if constexpr (std::is_same_v<std::variant_alternative_t<index, VariantType>, T>) {
        return index;
    } else {
        return variant_index<VariantType, T, index + 1>();
    }
} 

Это работает для меня, но теперь мне интересно, как это сделать по-старому без constexpr if, как структура.

0 голосов
/ 13 сентября 2018

Мы могли бы воспользоваться тем, что index() почти уже делает правильные вещи.

Мы не можем произвольно создавать экземпляры различных типов - мы не знали бы, как это сделать, и произвольные типы могут не быть литеральными типами. Но мы можем создавать экземпляры определенных типов, о которых мы знаем:

template <typename> struct tag { }; // <== this one IS literal

template <typename T, typename V>
struct get_index;

template <typename T, typename... Ts> 
struct get_index<T, std::variant<Ts...>>
    : std::integral_constant<size_t, std::variant<tag<Ts>...>(tag<T>()).index()>
{ };

То есть, чтобы найти индекс B в variant<A, B, C>, мы строим variant<tag<A>, tag<B>, tag<C>> с tag<B> и находим его индекс.

Это работает только с разными типами.

0 голосов
/ 13 сентября 2018

Один забавный способ сделать это - взять ваш variant<Ts...> и превратить его в пользовательскую иерархию классов, в которой все реализуют определенную статическую функцию-член с другим результатом, который вы можете запросить.

Другими словами, учитывая variant<A, B, C>, создайте иерархию, которая выглядит следующим образом:

struct base_A {
    static integral_constant<int, 0> get(tag<A>);
};
struct base_B {
    static integral_constant<int, 1> get(tag<B>);
};
struct base_C {
    static integral_constant<int, 2> get(tag<C>);
};
struct getter : base_A, base_B, base_C {
    using base_A::get, base_B::get, base_C::get;
};

И затем, decltype(getter::get(tag<T>())) является индексом (или не компилируется). Надеюсь, это имеет смысл.

<ч />

В реальном коде выше становится:

template <typename T> struct tag { };

template <std::size_t I, typename T>
struct base {
    static std::integral_constant<size_t, I> get(tag<T>);
};

template <typename S, typename... Ts>
struct getter_impl;

template <std::size_t... Is, typename... Ts>
struct getter_impl<std::index_sequence<Is...>, Ts...>
    : base<Is, Ts>...
{
    using base<Is, Ts>::get...;
};

template <typename... Ts>
struct getter : getter_impl<std::index_sequence_for<Ts...>, Ts...>
{ };

И как только вы установите геттер, на самом деле использовать его гораздо проще:

template <typename T, typename V>
struct get_index;

template <typename T, typename... Ts>
struct get_index<T, std::variant<Ts...>>
    : decltype(getter<Ts...>::get(tag<T>()))
{ };
<ч />

Это работает только в случае, когда типы различны. Если вам это нужно для работы с независимыми типами, то лучшее, что вы можете сделать, это, вероятно, линейный поиск?

template <typename T, typename>
struct get_index;

template <size_t I, typename... Ts> 
struct get_index_impl
{ };

template <size_t I, typename T, typename... Ts> 
struct get_index_impl<I, T, T, Ts...>
    : std::integral_constant<size_t, I>
{ };

template <size_t I, typename T, typename U, typename... Ts> 
struct get_index_impl<I, T, U, Ts...>
    : get_index_impl<I+1, T, Ts...>
{ };

template <typename T, typename... Ts> 
struct get_index<T, std::variant<Ts...>>
    : get_index_impl<0, T, Ts...>
{ };
...