Get index by type in std::variant
Asked Answered
C

7

31

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

Compurgation answered 12/9, 2018 at 20:52 Comment(4)
Aren't you just looking for std::variant::index()?Denson
@Denson I believe the OP is working on the type level, not on the value level, so that wouldn't work. Something like get_index_of<B, std::variant<A, B, C>> returning 1Ivanaivanah
@Ivanaivanah Ah, that makes sense. That's probably what they want.Denson
Good question also for std::tuple, IMHOTidewaiter
C
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.

Compurgation answered 12/9, 2018 at 21:23 Comment(2)
For C++20 the function can be consteval instead of constexpr.Ceilidh
Also I think the first if constexpr can never be true due to the preceding static_assert; unless I'm mistaken, that block can be removed.Ceilidh
W
31

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.

Warfield answered 12/9, 2018 at 21:25 Comment(10)
That's clever, huh.Lyssa
Great solution!Tidewaiter
Just wondering: wouldn't std::declval<T> do in place of tag<T>?Lusitania
@Lusitania 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.Warfield
Buggered in MSVS2017, sadly. Any ideas? :)Kilter
@Lusitania 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.Cosecant
...while we're at it, std::array<T, 0> is another functional equivalent.Cosecant
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...Briannabrianne
@Briannabrianne 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.Warfield
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)'.Briannabrianne
C
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.

Compurgation answered 12/9, 2018 at 21:23 Comment(2)
For C++20 the function can be consteval instead of constexpr.Ceilidh
Also I think the first if constexpr can never be true due to the preceding static_assert; unless I'm mistaken, that block can be removed.Ceilidh
W
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...>>.

Warfield answered 12/9, 2018 at 22:40 Comment(0)
L
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); 
}
Lubalubba answered 26/2, 2021 at 13:17 Comment(0)
W
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...>
{ };
Warfield answered 12/9, 2018 at 21:13 Comment(1)
Thanks for showing the implementation without if constexpr!Compurgation
T
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");
Tailor answered 13/9, 2018 at 1:44 Comment(6)
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...Warfield
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<I>. Because of that the .index() trick proposed in your answer will not compile. Also, variants cannot be in 'absence' state.Tailor
"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<int, variant<double>> and variant_index_v<double, variant<double>> both are 0.Warfield
Also not compiling matches what get<int>(make_tuple(1,1)) does, for instance.Warfield
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.Tailor
So... fix it, please.Warfield
L
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

Labefaction answered 4/5, 2021 at 10:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.