在 std::variant 中按类型获取索引
Get index by type in std::variant
标准库中是否有实用程序可以获取 std::variant
中给定 type 的 index?还是我应该自己做一个?也就是说,我想在 std::variant<A, B, C>
中获取 B
的索引,并让 return 1
.
相反的操作有std::variant_alternative
。当然,std::variant
的列表中可能有很多相同的类型,所以这个操作不是双射,但对我来说不是问题(我可以在列表中第一次出现类型,或者唯一类型在 std::variant
名单上)。
一个有趣的方法是将您的 variant<Ts...>
变成一个自定义的 class 层次结构,所有这些层次结构都实现了一个特定的静态成员函数,您可以查询不同的结果。
换句话说,给定 variant<A, B, C>
,创建一个如下所示的层次结构:
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;
};
然后,decltype(getter::get(tag<T>()))
是索引(或者不编译)。希望这是有道理的。
在实际代码中,上面变成:
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...>
{ };
一旦你建立了 getter,实际使用它就简单多了:
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>()))
{ };
这仅适用于类型不同的情况。如果您需要它与独立类型一起使用,那么您能做的最好的可能是线性搜索?
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...>
{ };
我找到了 this 元组的答案并稍微修改了它:
template<typename VariantType, typename T, std::size_t index = 0>
constexpr std::size_t variant_index() {
static_assert(std::variant_size_v<VariantType> > index, "Type not found in variant");
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>();
}
}
它对我有用,但现在我很好奇如何在没有 constexpr if 的情况下以旧方式将其作为结构。
几年后更新:我在这里的回答可能很酷,但 是正确的。这就是我今天解决这个问题的方法。
我们可以利用 index()
几乎已经在做正确的事情这一事实。
我们不能任意创建各种类型的实例 - 我们不知道该怎么做,而且任意类型可能不是文字类型。但是我们可以创建我们知道的特定类型的实例:
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()>
{ };
也就是说,要在variant<A, B, C>
中找到B
的索引,我们用tag<B>
构造一个variant<tag<A>, tag<B>, tag<C>>
并找到它的索引。
这只适用于不同的类型。
你也可以用折叠表达式来做到这一点:
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;
}
折叠表达式在我们第一次匹配类型时停止,此时我们停止递增 r
。这甚至适用于重复类型。如果未找到类型,则返回大小。在这种情况下,如果更可取,可以很容易地将其更改为 not return
,因为在 constexpr
函数中缺少 return
是不正确的。
如果您不想使用 variant
的实例,这里的参数可以改为 tag<variant<Ts...>>
.
我的两个美分解决方案:
template <typename T, typename... Ts>
constexpr std::size_t variant_index_impl(std::variant<Ts...>**)
{
std::size_t i = 0; ((!std::is_same_v<T, Ts> && ++i) && ...); return i;
}
template <typename T, typename V>
constexpr std::size_t variant_index_v = variant_index_impl<T>(static_cast<V**>(nullptr));
template <typename T, typename V, std::size_t... Is>
constexpr std::size_t variant_index_impl(std::index_sequence<Is...>)
{
return ((std::is_same_v<T, std::variant_alternative_t<Is, V>> * Is) + ...);
}
template <typename T, typename V>
constexpr std::size_t variant_index_v = variant_index_impl<T, V>(std::make_index_sequence<std::variant_size_v<V>>{});
如果您希望在查找不包含类型或重复类型时出现硬错误 - 这里是静态断言:
constexpr auto occurrences = (std::is_same_v<T, Ts> + ...);
static_assert(occurrences != 0, "The variant cannot have the type");
static_assert(occurrences <= 1, "The variant has duplicates of the type");
对于 Boost.Mp11,这是一个简短的单行代码:
template<typename Variant, typename T>
constexpr size_t IndexInVariant = mp_find<Variant, T>::value;
完整 example:
#include <variant>
#include <boost/mp11/algorithm.hpp>
using namespace boost::mp11;
template<typename Variant, typename T>
constexpr size_t IndexInVariant = mp_find<Variant, T>::value;
int main()
{
using V = std::variant<int,double, char, double>;
static_assert(IndexInVariant<V, int> == 0);
// for duplicates first idx is returned
static_assert(IndexInVariant<V, double> == 1);
static_assert(IndexInVariant<V, char> == 2);
// not found returns ".end()"/ or size of variant
static_assert(IndexInVariant<V, float> == 4);
// beware that const and volatile and ref are not stripped
static_assert(IndexInVariant<V, int&> == 4);
static_assert(IndexInVariant<V, const int> == 4);
static_assert(IndexInVariant<V, volatile int> == 4);
}
另一种看法:
#include <type_traits>
namespace detail {
struct count_index {
std::size_t value = 0;
bool found = false;
template <typename T, typename U>
constexpr count_index operator+(const std::is_same<T, U> &rhs)
{
if (found)
return *this;
return { value + !rhs, rhs};
}
};
}
template <typename Seq, typename T>
struct index_of;
template <template <typename...> typename Seq, typename... Ts, typename T>
struct index_of<Seq<Ts...>, T>: std::integral_constant<std::size_t, (detail::count_index{} + ... + std::is_same<T, Ts>{}).value> {
static_assert(index_of::value < sizeof...(Ts), "Sequence doesn't contain the type");
};
然后:
#include <variant>
struct A{};
struct B{};
struct C{};
using V = std::variant<A, B, C>;
static_assert(index_of<V, B>::value == 1);
或者:
static_assert(index_of<std::tuple<int, float, bool>, float>::value == 1);
标准库中是否有实用程序可以获取 std::variant
中给定 type 的 index?还是我应该自己做一个?也就是说,我想在 std::variant<A, B, C>
中获取 B
的索引,并让 return 1
.
相反的操作有std::variant_alternative
。当然,std::variant
的列表中可能有很多相同的类型,所以这个操作不是双射,但对我来说不是问题(我可以在列表中第一次出现类型,或者唯一类型在 std::variant
名单上)。
一个有趣的方法是将您的 variant<Ts...>
变成一个自定义的 class 层次结构,所有这些层次结构都实现了一个特定的静态成员函数,您可以查询不同的结果。
换句话说,给定 variant<A, B, C>
,创建一个如下所示的层次结构:
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;
};
然后,decltype(getter::get(tag<T>()))
是索引(或者不编译)。希望这是有道理的。
在实际代码中,上面变成:
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...>
{ };
一旦你建立了 getter,实际使用它就简单多了:
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>()))
{ };
这仅适用于类型不同的情况。如果您需要它与独立类型一起使用,那么您能做的最好的可能是线性搜索?
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...>
{ };
我找到了 this 元组的答案并稍微修改了它:
template<typename VariantType, typename T, std::size_t index = 0>
constexpr std::size_t variant_index() {
static_assert(std::variant_size_v<VariantType> > index, "Type not found in variant");
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>();
}
}
它对我有用,但现在我很好奇如何在没有 constexpr if 的情况下以旧方式将其作为结构。
几年后更新:我在这里的回答可能很酷,但
我们可以利用 index()
几乎已经在做正确的事情这一事实。
我们不能任意创建各种类型的实例 - 我们不知道该怎么做,而且任意类型可能不是文字类型。但是我们可以创建我们知道的特定类型的实例:
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()>
{ };
也就是说,要在variant<A, B, C>
中找到B
的索引,我们用tag<B>
构造一个variant<tag<A>, tag<B>, tag<C>>
并找到它的索引。
这只适用于不同的类型。
你也可以用折叠表达式来做到这一点:
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;
}
折叠表达式在我们第一次匹配类型时停止,此时我们停止递增 r
。这甚至适用于重复类型。如果未找到类型,则返回大小。在这种情况下,如果更可取,可以很容易地将其更改为 not return
,因为在 constexpr
函数中缺少 return
是不正确的。
如果您不想使用 variant
的实例,这里的参数可以改为 tag<variant<Ts...>>
.
我的两个美分解决方案:
template <typename T, typename... Ts>
constexpr std::size_t variant_index_impl(std::variant<Ts...>**)
{
std::size_t i = 0; ((!std::is_same_v<T, Ts> && ++i) && ...); return i;
}
template <typename T, typename V>
constexpr std::size_t variant_index_v = variant_index_impl<T>(static_cast<V**>(nullptr));
template <typename T, typename V, std::size_t... Is>
constexpr std::size_t variant_index_impl(std::index_sequence<Is...>)
{
return ((std::is_same_v<T, std::variant_alternative_t<Is, V>> * Is) + ...);
}
template <typename T, typename V>
constexpr std::size_t variant_index_v = variant_index_impl<T, V>(std::make_index_sequence<std::variant_size_v<V>>{});
如果您希望在查找不包含类型或重复类型时出现硬错误 - 这里是静态断言:
constexpr auto occurrences = (std::is_same_v<T, Ts> + ...);
static_assert(occurrences != 0, "The variant cannot have the type");
static_assert(occurrences <= 1, "The variant has duplicates of the type");
对于 Boost.Mp11,这是一个简短的单行代码:
template<typename Variant, typename T>
constexpr size_t IndexInVariant = mp_find<Variant, T>::value;
完整 example:
#include <variant>
#include <boost/mp11/algorithm.hpp>
using namespace boost::mp11;
template<typename Variant, typename T>
constexpr size_t IndexInVariant = mp_find<Variant, T>::value;
int main()
{
using V = std::variant<int,double, char, double>;
static_assert(IndexInVariant<V, int> == 0);
// for duplicates first idx is returned
static_assert(IndexInVariant<V, double> == 1);
static_assert(IndexInVariant<V, char> == 2);
// not found returns ".end()"/ or size of variant
static_assert(IndexInVariant<V, float> == 4);
// beware that const and volatile and ref are not stripped
static_assert(IndexInVariant<V, int&> == 4);
static_assert(IndexInVariant<V, const int> == 4);
static_assert(IndexInVariant<V, volatile int> == 4);
}
另一种看法:
#include <type_traits>
namespace detail {
struct count_index {
std::size_t value = 0;
bool found = false;
template <typename T, typename U>
constexpr count_index operator+(const std::is_same<T, U> &rhs)
{
if (found)
return *this;
return { value + !rhs, rhs};
}
};
}
template <typename Seq, typename T>
struct index_of;
template <template <typename...> typename Seq, typename... Ts, typename T>
struct index_of<Seq<Ts...>, T>: std::integral_constant<std::size_t, (detail::count_index{} + ... + std::is_same<T, Ts>{}).value> {
static_assert(index_of::value < sizeof...(Ts), "Sequence doesn't contain the type");
};
然后:
#include <variant>
struct A{};
struct B{};
struct C{};
using V = std::variant<A, B, C>;
static_assert(index_of<V, B>::value == 1);
或者:
static_assert(index_of<std::tuple<int, float, bool>, float>::value == 1);