"increment" `std::variant` 替代
"increment" `std::variant` alternative
我想递增/递减 std::variant
的替代类型,基本上像这样:
using var_t = std::variant</*...*/>;
var_t var;
var.emplace< (var.index()+1) % std::variant_size<var_t> >(); // "increment" case, wrapping for good measure
这里的问题是,虽然 emplace
期望 clang 的错误消息称为“明确指定的参数”,但 index
似乎不是 constexpr
。
明显的替代方案是这样的:
switch(var.index()){
0:
var.emplace<1>();
break;
1:
var.emplace<2>();
break;
// ...
variant_size<var_t>-1:
var.emplace<0>();
}
但这就是我个人所说的“极其丑陋”和“维护背后的巨大痛苦”(特别是因为我必须维护这些块的两个几乎副本递增和递减)。
有更好/“正确”的方法吗?
如果信息在任何方面都很重要,我将在 clang
上以 libstdc++
为目标 C++20
。
像往常一样,std::index_sequence
可能会有所帮助:
#include <variant>
template <typename... Ts, std::size_t... Is>
void next(std::variant<Ts...>& v, std::index_sequence<Is...>)
{
using Func = void (*)(std::variant<Ts...>&);
Func funcs[] = {
+[](std::variant<Ts...>& v){ v.template emplace<(Is + 1) % sizeof...(Is)>(); }...
};
funcs[v.index()](v);
}
template <typename... Ts>
void next(std::variant<Ts...>& v)
{
next(v, std::make_index_sequence<sizeof...(Ts)>());
}
注意:对于prev
,Is + 1
应替换为Is + sizeof...(Is) - 1
。
另一种可能的解决方案(在我看来)比@Jarod42 的方案更难看,它依赖于在编译时使用 std::visit
中的模板化 lambda 查找索引:
#include <variant>
template <class T, std::size_t I, class... Args>
struct index_of_;
template <class T, std::size_t I, class... Args>
struct index_of_<T, I, T, Args... >: std::integral_constant<std::size_t, I> {};
template <class T, std::size_t I, class U, class... Args>
struct index_of_<T, I, U, Args... >: index_of_<T, I + 1, Args... > {};
template <class T, class... Args>
struct next_index: std::integral_constant<
std::size_t,
(index_of_<T, 0, Args... >::value + 1) % sizeof... (Args)> {};
template <class... Args>
void increment(std::variant<Args...>& variant) {
// prior to C++20, you can use [&](auto const& arg) and retrieve T
// via std::decay_t<decltype(arg)>,
std::visit([&]<class T>(T const&) {
variant.template emplace<next_index<T, Args...>::value>();
}, variant);
}
与@Jarod42 解决方案不同,如果您的变体中有重复类型,则此解决方案将不起作用。
The problem here is that while emplace
expects what clang's error
message calls an "explicitly-specified argument", index
does not
appear to be constexpr
.
可以使用std::variant
将运行-timeindex
转换为编译时常量,即std::integral_constant
.
#include <variant>
#include <array>
template<std::size_t N>
using IC = std::integral_constant<std::size_t, N>;
template<std::size_t N>
constexpr auto gen_indices = []<std::size_t... Is>(
std::index_sequence<Is...>) {
return std::array{std::variant<IC<Is>...>(IC<Is>{})...};
}(std::make_index_sequence<N>{});
template <typename Variant>
constexpr void increment(Variant& v) {
constexpr auto size = std::variant_size_v<Variant>;
constexpr auto& indices = gen_indices<size>;
std::visit(
[&v](auto index) { v.template emplace<(index+1) % size>(); },
indices[v.index()]);
}
这是许多需要基于索引的访问的情况之一。我们可以一般地使用 Boost.Mp11:
来写
template <typename F, typename Variant>
decltype(auto) visit_with_index(F&& f, Variant&& v) {
constexpr size_t N = mp_size<std::remove_cvref_t<Variant>>;
return mp_with_index<N>(v.index(), [&](auto I){
return f(I, std::get<I>(v));
});
}
这会将索引(这是一些整数常量)和元素都传递给函数。现在我们可以写:
template <typename... Args>
void next_alt(std::variant<Args...>& v) {
visit_with_index([&](auto I, auto&&){
v.emplace<(I+1) % sizeof...(Args)>();
}, v);
}
作为奖励,mp_with_index
是一个开关,因此它比 std::visit
具有更好的性能,所以它无论如何都是一个很好的解决方案。请注意,这不会处理 valueless_by_exception
,但如果需要,可以直接添加到顶部。
我想递增/递减 std::variant
的替代类型,基本上像这样:
using var_t = std::variant</*...*/>;
var_t var;
var.emplace< (var.index()+1) % std::variant_size<var_t> >(); // "increment" case, wrapping for good measure
这里的问题是,虽然 emplace
期望 clang 的错误消息称为“明确指定的参数”,但 index
似乎不是 constexpr
。
明显的替代方案是这样的:
switch(var.index()){
0:
var.emplace<1>();
break;
1:
var.emplace<2>();
break;
// ...
variant_size<var_t>-1:
var.emplace<0>();
}
但这就是我个人所说的“极其丑陋”和“维护背后的巨大痛苦”(特别是因为我必须维护这些块的两个几乎副本递增和递减)。
有更好/“正确”的方法吗?
如果信息在任何方面都很重要,我将在 clang
上以 libstdc++
为目标 C++20
。
像往常一样,std::index_sequence
可能会有所帮助:
#include <variant>
template <typename... Ts, std::size_t... Is>
void next(std::variant<Ts...>& v, std::index_sequence<Is...>)
{
using Func = void (*)(std::variant<Ts...>&);
Func funcs[] = {
+[](std::variant<Ts...>& v){ v.template emplace<(Is + 1) % sizeof...(Is)>(); }...
};
funcs[v.index()](v);
}
template <typename... Ts>
void next(std::variant<Ts...>& v)
{
next(v, std::make_index_sequence<sizeof...(Ts)>());
}
注意:对于prev
,Is + 1
应替换为Is + sizeof...(Is) - 1
。
另一种可能的解决方案(在我看来)比@Jarod42 的方案更难看,它依赖于在编译时使用 std::visit
中的模板化 lambda 查找索引:
#include <variant>
template <class T, std::size_t I, class... Args>
struct index_of_;
template <class T, std::size_t I, class... Args>
struct index_of_<T, I, T, Args... >: std::integral_constant<std::size_t, I> {};
template <class T, std::size_t I, class U, class... Args>
struct index_of_<T, I, U, Args... >: index_of_<T, I + 1, Args... > {};
template <class T, class... Args>
struct next_index: std::integral_constant<
std::size_t,
(index_of_<T, 0, Args... >::value + 1) % sizeof... (Args)> {};
template <class... Args>
void increment(std::variant<Args...>& variant) {
// prior to C++20, you can use [&](auto const& arg) and retrieve T
// via std::decay_t<decltype(arg)>,
std::visit([&]<class T>(T const&) {
variant.template emplace<next_index<T, Args...>::value>();
}, variant);
}
与@Jarod42 解决方案不同,如果您的变体中有重复类型,则此解决方案将不起作用。
The problem here is that while
emplace
expects what clang's error message calls an "explicitly-specified argument",index
does not appear to beconstexpr
.
可以使用std::variant
将运行-timeindex
转换为编译时常量,即std::integral_constant
.
#include <variant>
#include <array>
template<std::size_t N>
using IC = std::integral_constant<std::size_t, N>;
template<std::size_t N>
constexpr auto gen_indices = []<std::size_t... Is>(
std::index_sequence<Is...>) {
return std::array{std::variant<IC<Is>...>(IC<Is>{})...};
}(std::make_index_sequence<N>{});
template <typename Variant>
constexpr void increment(Variant& v) {
constexpr auto size = std::variant_size_v<Variant>;
constexpr auto& indices = gen_indices<size>;
std::visit(
[&v](auto index) { v.template emplace<(index+1) % size>(); },
indices[v.index()]);
}
这是许多需要基于索引的访问的情况之一。我们可以一般地使用 Boost.Mp11:
来写template <typename F, typename Variant>
decltype(auto) visit_with_index(F&& f, Variant&& v) {
constexpr size_t N = mp_size<std::remove_cvref_t<Variant>>;
return mp_with_index<N>(v.index(), [&](auto I){
return f(I, std::get<I>(v));
});
}
这会将索引(这是一些整数常量)和元素都传递给函数。现在我们可以写:
template <typename... Args>
void next_alt(std::variant<Args...>& v) {
visit_with_index([&](auto I, auto&&){
v.emplace<(I+1) % sizeof...(Args)>();
}, v);
}
作为奖励,mp_with_index
是一个开关,因此它比 std::visit
具有更好的性能,所以它无论如何都是一个很好的解决方案。请注意,这不会处理 valueless_by_exception
,但如果需要,可以直接添加到顶部。