Как проверить, поддерживает ли std:: variant определенный тип

У меня есть класс с std::variant. Этот тип std::variant разрешен только для хранения определенного списка типов.

У меня есть функции шаблона, которые позволяют пользователю класса вставлять различные значения в std::unordered_map, на карте хранятся значения этого варианта. I.e., пользователю разрешено вставлять значения только в том случае, если он указан в конкретном списке типов. Однако я не хочу, чтобы пользователь мог сам определять этот список типов.

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

Вы увидите выше, где есть ???, как я могу проверить? Некоторая комбинация std::disjunction с std::is_same?

Как

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

Чтобы было ясно, я бы предпочел не проверять каждый разрешенный тип вручную.

Ответ 1

Изменить: Я действительно копаю вашу идею std::disjunction, и она абсолютно работает. Вам просто нужно извлечь список типов, используя специализированную специализацию.

Весь мой рекурсивный беспорядок в старой школе становится просто:

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

Оригинальный ответ: Вот простой шаблон, который это выполняет. Он работает, возвращая false для пустых списков типов. Для непустых списков он возвращает true, если первый тип проходит std::is_same<> и рекурсивно вызывает себя со всеми, кроме первого типа в противном случае.

#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. Удостоверьтесь, что вы снимаете cv и контрольные квалификаторы от T до вызова этого (или добавьте удаление для самого шаблона). Это действительно зависит от ваших потребностей.

Ответ 2

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

Затем используйте is_one_of(type_v<VariantType>, type_v<T>) в enable_if.

Ответ 3

Вы можете избежать использования std::enable_if_t и вместо этого использовать классические выражения SFINAE на основе decltype, как в следующем примере:

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

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

Если вы переключите комментарий на последнюю строку, вы получите ошибку времени компиляции для char не является типом, принятым вашим вариантом.

Преимущества этого решения в том, что он прост и вам не нужно включать type_traits (предоставленный, вы должны включить utility), а также использовать класс поддержки для получения значения bool для тестирования из это.
Конечно, вы можете настроить тип возврата соответствующим требованиям для каждой функции.

Посмотрите и запустите wandbox.


В противном случае, если вы можете придерживаться ограничений std::holds_alternative (вызов плохо сформирован, если тип сравнивается более одного раза в списке параметров варианта), обратите внимание, что это функция constexpr, и это просто делает то, что вы хотите:

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

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

Как и выше, переключите комментарий, и вы получите ошибку времени компиляции, как ожидалось.

Посмотрите и запустите wandbox.

Ответ 4

Поскольку вы уже используете С++ 17, сгиб-выражения делают это проще:

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

Для дополнительной читаемости вы можете добавить псевдоним в свой класс:

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

Ответ 5

Вы можете попробовать использовать SFINAE, построив VariantType из типа T.

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

Или используйте std::is_constructible<VariantType, T>. В конце концов вы, вероятно, захотите узнать, можете ли вы назначить/инициализировать тип, а не если тип на самом деле является одним из типов вариантов (что более ограничительно).

Ответ 6

#include <type_traits>
#include <variant>


template <typename, typename... T0ToN>
struct is_one_of;

template <typename T>
struct is_one_of<T> : public std::false_type
{
};

template <typename T, typename... T1toN>
struct is_one_of<T, T, T1toN...> : public std::true_type
{
};

template <typename T, typename P, typename... T1toN>
struct is_one_of<T, P, T1toN...> : is_one_of<T, T1toN...>
{
};

template <typename Type, typename ... Others>
struct is_in_variant : public std::false_type {};

template <typename Type, typename ... Others>
struct is_in_variant<Type, std::variant<Others...>> : public is_one_of<Type, Others...>
{};


int main()
{
    std::variant<int, float> v;
    return is_in_variant<double, std::variant<int, float>>::value ? 4 : 8;
}