Как проверить, поддерживает ли 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;
}