如何检查 std::variant 是否可以容纳某种类型

How do I check if an std::variant can hold a certain type

本文关键字:类型 种类 是否 variant 何检查 检查 std      更新时间:2023-10-16

我有一个std::variant的类。此std::variant类型仅允许保存特定的类型列表。

我有模板函数,允许类的用户将各种值插入std::unordered_map,映射包含此变体类型的值。 即,仅当用户的类型位于特定类型列表中时,才允许用户插入值。但是,我不希望用户能够自己定义此类型列表。

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::disjunctionstd::is_same的某种组合?

喜欢

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

为了清楚起见,我宁愿不必手动检查每种允许的类型。

编辑:我实际上挖掘了你的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。对于非空列表,如果第一个类型通过std::is_same<>,则返回true,否则使用除第一个类型之外的所有类型递归调用自身。

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

注意:在调用它之前,请确保从 T 中删除 cv 和引用限定符(或将剥离添加到模板本身(。这取决于您的需求,真的。

由于您已经在使用 C++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)
{ ... }
};
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> || ...); 
}

然后在enable_if中使用is_one_of(type_v<VariantType>, type_v<T>)

您可以避免使用std::enable_if_t,而是使用基于decltype的经典 SFINAE 表达式,如以下示例所示:

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

如果将注释切换到最后一行,则会收到编译时错误char因为该类型不是变体接受的类型。

此解决方案的优点是它很简单,您既不必包含type_traits(当然,您必须包含utility(,也不必使用支持类来获取布尔值以进行测试。
当然,您可以根据每个函数的要求相应地调整返回类型。

看到它在魔杖盒上启动并运行。


否则,如果您可以坚持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's ok
}
};
int main() {
S s;
s.f<int>();
//s.f<char>();
}

如上所述,切换注释,您将按预期收到编译时错误。

看到它在魔杖盒上启动并运行。

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

您可以通过从T类型构造VariantType来尝试使用 SFINAE。

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>.毕竟,您可能想知道是否可以从类型中分配/初始化,而不是类型是否实际上是变体类型之一(这更具限制性(。