29

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

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Bargor
  • 622
  • 1
  • 6
  • 13

7 Answers7

28

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • That's clever, huh. – HolyBlackCat Sep 12 '18 at 21:29
  • Great solution! – max66 Sep 12 '18 at 21:35
  • Just wondering: wouldn't std::declval do in place of tag? – R2RT Sep 13 '18 at 12:40
  • 1
    @R2RT No. That wouldn't address the issue of some types not being literal types, and you can't use `declval` in an evaluated context anyway. – Barry Sep 13 '18 at 12:45
  • Buggered in MSVS2017, sadly. [Any ideas](https://stackoverflow.com/q/53651609/560648)? :) – Lightness Races in Orbit Dec 06 '18 at 12:31
  • 2
    @R2RT Starting with C++17, the standard library has std::in_place_type_t that serves precisely the same purpose as the tag struct in this example. – Pavel Kirienko Jan 28 '22 at 14:29
  • ...while we're at it, `std::array` is another functional equivalent. – Pavel Kirienko Jan 28 '22 at 14:50
  • Again a few years later: Don't agree on the update at all! The referred answer uses Boost – this is a *potential* solution, but not the Holy Grail, which the wording '*correct answer'* would imply... – Aconcagua May 03 '22 at 09:59
  • @Aconcagua If you look through my answers, you'll see that I have quite a few answers to metaprogramming questions that indicate that Boost.Mp11 gives you a one-liner solution to... most problems. In that sense, it is the correct solution: you use a library that gives you all the tools to solve your problem, quickly and efficiently. It's fun to work through clever solutions to all these problems -- but it's not a great use of time to come up with a bespoke, probably-not-reusable solution every time. Mp11 just works and is a great resource, my answer here simply predates me knowing about it. – Barry May 03 '22 at 14:06
  • No matter how short a proposed solution might appear – 'correct' in sense of sole and only solution would apply if it was part of the *standard* library only (if at all...). There might be competing libraries or boost not being allowed for being criticised too heavily or other reasons (I personally am not affected of such restrictions – I know of others that are, though). Maybe I might appear pedantic, but I'd really prefer wording like *'simpler/easier/shorter'* or maybe even *'superior (if applicable)*'. – Aconcagua May 03 '22 at 15:04
17

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.

Nykakin
  • 8,657
  • 2
  • 29
  • 42
Bargor
  • 622
  • 1
  • 6
  • 13
8

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

Barry
  • 286,269
  • 29
  • 621
  • 977
5

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); 
}
NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277
4

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...>
{ };
Barry
  • 286,269
  • 29
  • 621
  • 977
1

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");
Nikita Kniazev
  • 3,728
  • 2
  • 16
  • 30
  • You should note that the second one won't work with duplicate types. And you can't distinguish between absence and index 0. You can fix the second one by, for instance, multiplying by `Is+1` instead and then subtracting 1. But the duplicate problem would require adding another local flag or something, which is now getting to complicated imo... – Barry Sep 13 '18 at 02:00
  • Variants with duplicate types are strange beasts (it is against sum types nature), you can only have one with value of duplicate type by default constructing or constructing it with `in_place_index_t`. Because of that the `.index()` trick proposed in your answer will not compile. Also, variants cannot be in 'absence' state. – Nikita Kniazev Sep 13 '18 at 11:50
  • "Not compile" is a better choice than "give wrong/misleading answer." I'm also not talking about absent state, I'm talking about absent types: your `variant_index_v>` and `variant_index_v>` both are `0`. – Barry Sep 13 '18 at 11:56
  • Also not compiling matches what `get(make_tuple(1,1))` does, for instance. – Barry Sep 13 '18 at 11:59
  • You know that it could be fixed with a static assert :) The static assert will be even more meaningful than a compiler error of not finding a suitable constructor. As for me, instantiating variant with duplicate types should not succeed, as it is a logical error, loophole in the standard. – Nikita Kniazev Sep 13 '18 at 12:07
  • So... fix it, please. – Barry Sep 13 '18 at 12:13
1

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

Fabio A.
  • 2,517
  • 26
  • 35