Get index by type in std::variant

前端 未结 5 1187
暗喜
暗喜 2020-12-10 13:19

Is there a utility in the standard library to get the index of a given type in std::variant? Or should I make one for myself? That is, I want

相关标签:
5条回答
  • 2020-12-10 13:54

    One fun way to do this is to take your variant<Ts...> and turn it into a custom class hierarchy that all implement a particular static member function with a different result that you can query.

    In other words, given variant<A, B, C>, create a hierarchy that looks like:

    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;
    };
    

    And then, decltype(getter::get(tag<T>())) is the index (or doesn't compile). Hopefully that makes sense.


    In real code, the above becomes:

    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...>
    { };
    

    And once you establish a getter, actually using it is much more straightforward:

    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>()))
    { };
    

    That only works in the case where the types are distinct. If you need it to work with independent types, then the best you can do is probably a linear search?

    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...>
    { };
    
    0 讨论(0)
  • 2020-12-10 14:06

    I found this answer for tuple and slightly modificated it:

    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>();
        }
    } 
    

    It works for me, but now I'm curious how to do it in old way without constexpr if, as a structure.

    0 讨论(0)
  • 2020-12-10 14:14

    You can also do this with a fold expression:

    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;
    }
    

    The fold expression stops the first time we match a type, at which point we stop incrementing r. This works even with duplicate types. If a type is not found, the size is returned. This could be easily changed to not return in this case if that's preferable, since missing return in a constexpr function is ill-formed.

    If you dont want to take an instance of variant, the argument here could instead be a tag<variant<Ts...>>.

    0 讨论(0)
  • 2020-12-10 14:18

    We could take advantage of the fact that index() almost already does the right thing.

    We can't arbitrarily create instances of various types - we wouldn't know how to do it, and arbitrary types might not be literal types. But we can create instances of specific types that we know about:

    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()>
    { };
    

    That is, to find the index of B in variant<A, B, C> we construct a variant<tag<A>, tag<B>, tag<C>> with a tag<B> and find its index.

    This only works with distinct types.

    0 讨论(0)
  • 2020-12-10 14:19

    My two cents solutions:

    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>>{});
    

    If you wish a hard error on lookups of not containing type or duplicate type - here are static asserts:

        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 讨论(0)
提交回复
热议问题