web-dev-qa-db-fra.com

Récupère l'index par type dans std :: variant

Y at-il un utilitaire dans la bibliothèque standard pour obtenir le index d'un type donné dans std::variant? Ou devrais-je en faire un pour moi? C'est-à-dire que je veux obtenir l'index de B dans std::variant<A, B, C> et avoir ce return 1.

Il y a std::variant_alternative pour l'opération inverse. Bien sûr, il pourrait y avoir beaucoup de mêmes types sur la liste de std::variant, donc cette opération n'est pas une bijection, mais ce n'est pas un problème pour moi (je peux avoir la première occurrence de type sur liste, ou des types uniques sur std::variant liste).

7
Bargor

J'ai trouvé this answer for Tuple et l'ai légèrement modifié:

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

Cela fonctionne pour moi, mais maintenant je suis curieux de savoir comment le faire de manière ancienne sans constexpr if, en tant que structure.

2
Bargor

Nous pourrions tirer parti du fait que index() fait presque déjà la bonne chose.

Nous ne pouvons pas créer arbitrairement différents types d'instances - nous ne saurions pas comment le faire et les types arbitraires peuvent ne pas être des types littéraux. Mais nous pouvons créer des instances de types spécifiques que nous connaissons:

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

C'est-à-dire que pour trouver l'index de B dans variant<A, B, C>, nous construisons un variant<tag<A>, tag<B>, tag<C>> avec un tag<B> et trouvons son index. 

Cela ne fonctionne qu'avec des types distincts. 

10
Barry

Vous pouvez également le faire avec une expression de pli:

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

L'expression pli s'arrête la première fois que nous correspondons à un type, moment auquel nous arrêtons d'incrémenter r. Cela fonctionne même avec les types de doublons. Si un type n'est pas trouvé, la taille est retournée. Cela pourrait facilement être changé en not return dans ce cas si cela est préférable, car manquer return dans une fonction constexpr est mal formé.

Si vous ne voulez pas prendre une instance de variant, l'argument ici pourrait plutôt être un tag<variant<Ts...>>.

2
Barry

Une façon amusante de faire cela consiste à transformer votre variant<Ts...> en une hiérarchie de classes personnalisée qui implémente toutes une fonction de membre statique particulière avec un résultat différent que vous pouvez interroger.

En d'autres termes, étant donné variant<A, B, C>, créez une hiérarchie ressemblant à:

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

Et puis, decltype(getter::get(tag<T>())) est l'index (ou ne compile pas). Espérons que cela a du sens.


En code réel, ce qui précède devient:

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

Et une fois que vous avez créé un getter, son utilisation est beaucoup plus simple:

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

Cela ne fonctionne que dans le cas où les types sont distincts. Si vous avez besoin de travailler avec des types indépendants, le mieux que vous puissiez faire est probablement une recherche linéaire.

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