web-dev-qa-db-fra.com

Comment vérifier si un std :: variant peut contenir un certain type

J'ai une classe avec un std::variant dedans. Ce type std::variant est uniquement autorisé à contenir une liste spécifique de types.

J'ai des fonctions de modèle qui permettent à l'utilisateur de la classe d'insérer différentes valeurs dans un std::unordered_map, la carte contient les valeurs de ce type variant. En d'autres termes, l'utilisateur n'est autorisé à insérer des valeurs que si son type figure dans la liste spécifique des types. Cependant, je ne veux pas que l'utilisateur puisse définir cette liste de types eux-mêmes.

class GLCapabilities
{
public:
    using VariantType = std::variant<GLint64>;  // in future this would have other types

    template <typename T>
    std::enable_if_t<???> AddCapability(const GLenum parameterName)
    {
        if(m_capabilities.count(parameterName) == 0)
        {
            /*... get correct value of type T ... */
            m_capabilities.insert(parameterName,value);
        }
    }

    template<typename T>
    std::enable_if_t<???,T> GetCapability(const GLenum parameterName) const
    {
        auto itr = m_capabilities.find(parameterName);
        if(std::holds_alternative<T>(*itr))
            return std::get<T>(*itr);

        return T;
    }

private:
    std::unordered_map<GLenum,VariantType> m_capabilities;
};

Vous verrez ci-dessus où se trouve le ???, comment puis-je vérifier? Une certaine combinaison de std::disjunction avec std::is_same?

Comme

std::enable_if<std::disjunction<std::is_same<T,/*Variant Types???*/>...>>

Pour que ce soit clair, je préférerais ne pas avoir à vérifier manuellement chaque type autorisé.

15
NeomerArcana

Edit: Je creuse en fait votre idée std::disjunction et elle fonctionne parfaitement. Il vous suffit d'extraire la liste de types à l'aide de la spécialisation de modèles.

Mon désordre récursif de la vieille école devient tout simplement:

template<typename T, typename VARIANT_T>
struct isVariantMember;

template<typename T, typename... ALL_T>
struct isVariantMember<T, std::variant<ALL_T...>> 
  : public std::disjunction<std::is_same<T, ALL_T>...> {};

Réponse originale: Voici un modèle simple qui accomplit cela. Cela fonctionne en retournant false pour les listes de types vides. Pour les listes non vides, il retourne true si le premier type passe std::is_same<> et s'invoque récursivement avec tout sauf le premier type.

#include <vector>
#include <Tuple>
#include <variant>

// Main lookup logic of looking up a type in a list.
template<typename T, typename... ALL_T>
struct isOneOf : public std::false_type {};

template<typename T, typename FRONT_T, typename... REST_T>
struct isOneOf<T, FRONT_T, REST_T...> : public 
  std::conditional<
    std::is_same<T, FRONT_T>::value,
    std::true_type,
    isOneOf<T, REST_T...>
  >::type {};

// Convenience wrapper for std::variant<>.
template<typename T, typename VARIANT_T>
struct isVariantMember;

template<typename T, typename... ALL_T>
struct isVariantMember<T, std::variant<ALL_T...>> : public isOneOf<T, ALL_T...> {};

// Example:
int main() {
  using var_t = std::variant<int, float>;

  bool x = isVariantMember<int, var_t>::value; // x == true
  bool y = isVariantMember<double, var_t>::value; // y == false

  return 0;
}

N.B. Assurez-vous de supprimer les qualificatifs cv et référence de T avant de l'invoquer (ou d'ajouter la suppression au modèle lui-même). Cela dépend de vos besoins, vraiment.

10
Frank
template <class T> struct type {};
template <class T> constexpr type<T> type_v{};

template <class T, class...Ts, template<class...> class Tp>
constexpr bool is_one_of(type<Tp<Ts...>>, type<T>) {
    return (std::is_same_v<Ts, T> || ...); 
}

Ensuite, utilisez is_one_of(type_v<VariantType>, type_v<T>) dans le enable_if

4
T.C.

Vous pouvez éviter d'utiliser std::enable_if_t et utiliser à la place des expressions SFINAE classiques à decltype comme celle de l'exemple suivant:

#include<variant>
#include<utility>

struct S {
    using type = std::variant<int, double>;

    template<typename U>
    auto f()
    -> decltype(std::declval<type>().emplace<U>(), void()) {
        // that's ok
    }
};

int main() {
    S s;
    s.f<int>();
    //s.f<char>();
}

Si vous basculez le commentaire sur la dernière ligne, vous obtenez une erreur de temps de compilation pour char qui n'est pas un type accepté par votre variante.

L'avantage de cette solution est que c'est simple et qu'il n'est pas nécessaire d'inclure type_traits (d'accord, vous devez inclure utility) ni d'utiliser une classe de support pour obtenir une valeur bool à tester.
Bien entendu, vous pouvez ajuster le type de retour en fonction de vos besoins pour chaque fonction.

Voyez-le en marche sur wandbox .


Sinon, si vous pouvez vous en tenir aux limitations de std::holds_alternative (l'appel est mal formé si le type se compare plus d'une fois dans la liste des paramètres de la variante), notez qu'il s'agit d'une fonction constexpr et qu'elle fait juste ce que vous voulez:

#include<type_traits>
#include<variant>
#include<utility>

struct S {
    using type = std::variant<int, double>;

    template<typename U>
    std::enable_if_t<std::holds_alternative<U>(type{})>
    f() {
        // that's ok
    }
};

int main() {
    S s;
    s.f<int>();
    //s.f<char>();
}

Comme ci-dessus, basculez le commentaire et vous obtiendrez une erreur de compilation comme prévu.

Voyez-le en marche sur wandbox .

2
skypjack

Puisque vous utilisez déjà C++ 17, les expressions de plis facilitent cela:

template <class T, class U> struct is_one_of;

template <class T, class... Ts> 
struct is_one_of<T, std::variant<Ts...>>
: std::bool_constant<(std::is_same_v<T, Ts> || ...)>
{ };

Pour plus de lisibilité, vous pouvez ajouter un alias dans votre classe:

class GLCapabilities
{
public:
    using VariantType = std::variant<GLint64>;  // in future this would have other types
    template <class T> using allowed = is_one_of<T, VariantType>;

    template <typename T>
    std::enable_if_t<allowed<T>{}> AddCapability(const GLenum parameterName)
    { ... }
};
1
Barry

Vous pouvez essayer d'utiliser SFINAE en construisant la variable VariantType à partir du type T.

template <typename T, typename = VariantType(std::declval<T>())>
void AddCapability(T const& t); // not sure how you want to use it.

Ou utilisez std::is_constructible<VariantType, T>. Après tout, vous voudrez probablement savoir si vous pouvez affecter/initialiser à partir du type, et non si le type est en réalité un des types variant (ce qui est plus restrictif).

0
alfC