wake-up-neo.com

Index nach Typ in std :: variant abrufen

Gibt es ein Dienstprogramm in der Standardbibliothek, um den index eines gegebenen type in std::variant zu erhalten? Oder soll ich mir eins machen? Das heißt, ich möchte den Index von B in std::variant<A, B, C> bekommen und diesen 1 zurückgeben.

Es gibt std::variant_alternative für die entgegengesetzte Operation. Natürlich könnte es viele gleiche Typen in der Liste von std::variant geben, daher ist diese Operation keine Doppelwahl, aber für mich ist das kein Problem (ich kann das erste Auftreten eines Typs in der Liste oder eindeutige Typen in der std::variant-Liste haben).

7
Bargor

Ich habe this answer für Tuple gefunden und etwas modifiziert:

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

Es funktioniert für mich, aber jetzt bin ich neugierig, wie man es auf alte Weise ohne constexpr macht, wenn als Struktur.

2
Bargor

Wir könnten die Tatsache ausnutzen, dass index() fast schon das Richtige tut.

Instanzen verschiedener Typen können nicht willkürlich erstellt werden - wir wissen nicht, wie das geht, und beliebige Typen sind möglicherweise keine Literaltypen. Wir können jedoch Instanzen bestimmter Typen erstellen, die uns bekannt sind:

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

Das heißt, um den Index von B in variant<A, B, C> zu finden, konstruieren wir einen variant<tag<A>, tag<B>, tag<C>> mit einem tag<B> und ermitteln dessen Index. 

Dies funktioniert nur bei unterschiedlichen Typen. 

10
Barry

Sie können dies auch mit einem Falzausdruck tun:

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

Der fold-Ausdruck stoppt, wenn wir zum ersten Mal einen Typ zuordnen. An diesem Punkt hören wir auf, r zu erhöhen. Dies funktioniert auch bei doppelten Typen. Wenn kein Typ gefunden wird, wird die Größe zurückgegeben. Dies kann in diesem Fall leicht in nicht return geändert werden, wenn dies vorzuziehen ist, da das Fehlen von return in einer constexpr-Funktion schlecht gebildet wird.

Wenn Sie keine Instanz von variant verwenden möchten, könnte das Argument hier stattdessen ein tag<variant<Ts...>> sein.

2
Barry

Eine unterhaltsame Möglichkeit, dies zu tun, besteht darin, Ihren variant<Ts...> in eine benutzerdefinierte Klassenhierarchie umzuwandeln, die alle eine bestimmte statische Memberfunktion mit einem anderen Ergebnis implementiert, das Sie abfragen können.

Mit anderen Worten, mit variant<A, B, C> erstellen Sie eine Hierarchie, die wie folgt aussieht:

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

Und dann ist decltype(getter::get(tag<T>())) der Index (oder kompiliert nicht). Hoffentlich macht das Sinn.


In echtem Code wird das Obige zu:

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

Und wenn Sie erst einmal einen Getter erstellt haben, ist die Verwendung viel einfacher:

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

Das funktioniert nur, wenn die Typen unterschiedlich sind. Wenn Sie mit unabhängigen Typen arbeiten möchten, ist das Beste, was Sie tun können, wahrscheinlich eine lineare Suche?

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