Get index by type in std::variant

Viewed 8676

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 to get the index of B in std::variant<A, B, C> and have that return 1.

There is std::variant_alternative for the opposite operation. Of course, there could be many same types on std::variant's list, so this operation is not a bijection, but it isn't a problem for me (I can have first occurrence of type on list, or unique types on std::variant list).

7 Answers

Update a few years later: My answer here may be a cool answer, but this is the correct one. That is how I would solve this problem today.


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.

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() {
    static_assert(std::variant_size_v<VariantType> > index, "Type not found in variant");
    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.

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...>>.

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

With Boost.Mp11 this is a short, one-liner:

template<typename Variant, typename T>
constexpr size_t IndexInVariant = mp_find<Variant, T>::value;

Full example:

#include <variant>
#include <boost/mp11/algorithm.hpp>

using namespace boost::mp11;

template<typename Variant, typename T>
constexpr size_t IndexInVariant = mp_find<Variant, T>::value;

int main()
{
    using V = std::variant<int,double, char, double>;
    static_assert(IndexInVariant<V, int> == 0);
    // for duplicates first idx is returned
    static_assert(IndexInVariant<V, double> == 1);
    static_assert(IndexInVariant<V, char> == 2);
    // not found returns ".end()"/ or size of variant
    static_assert(IndexInVariant<V, float> == 4); 
    // beware that const and volatile and ref are not stripped
    static_assert(IndexInVariant<V, int&> == 4); 
    static_assert(IndexInVariant<V, const int> == 4); 
    static_assert(IndexInVariant<V, volatile int> == 4); 
}

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");

Another take on it:

#include <type_traits>

namespace detail {
    struct count_index {
        std::size_t value = 0;
        bool found = false;
    
        template <typename T, typename U>
        constexpr count_index operator+(const std::is_same<T, U> &rhs)
        {
            if (found)
                return *this;
    
            return { value + !rhs, rhs};
        }
    };
}

template <typename Seq, typename T>
struct index_of;

template <template <typename...> typename Seq, typename... Ts, typename T>
struct index_of<Seq<Ts...>, T>: std::integral_constant<std::size_t, (detail::count_index{} + ... + std::is_same<T, Ts>{}).value> {
    static_assert(index_of::value < sizeof...(Ts), "Sequence doesn't contain the type");
};

And then:

#include <variant>

struct A{};
struct B{};
struct C{};
using V = std::variant<A, B, C>;

static_assert(index_of<V, B>::value == 1);

Or:

static_assert(index_of<std::tuple<int, float, bool>, float>::value == 1);

See on godbolt: https://godbolt.org/z/7ob6veWGr

Related