I want to increment / decrement a std::variant
's type alternative, essentially like so:
using var_t = std::variant</*...*/>;
var_t var;
var.emplace< (var.index() 1) % std::variant_size<var_t> >(); // "increment" case, wrapping for good measure
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
.
The obvious alternative would be something like this:
switch(var.index()){
0:
var.emplace<1>();
break;
1:
var.emplace<2>();
break;
// ...
variant_size<var_t>-1:
var.emplace<0>();
}
But that's what I personally would call "extremely ugly" and "a massive pain in the behind to maintain" (especially since I'd have to maintain two almost-copies of those blocks off-by-two for both incrementing and decrementing).
Is there a better / "correct" way of doing this?
In case that information is important in any way, I'm targeting C 20
on clang
with libstdc
.
CodePudding user response:
Another possible solution that's (in my opinion) a bit uglier than @Jarod42's one and rely on finding the index at compile time using a templated lambda in std::visit
:
#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) {
std::visit([&](auto const& arg) {
variant.template emplace<
next_index<std::decay_t<decltype(arg)>, Args...>::value>();
}, variant);
}
Unlike @Jarod42 solution, this solution will not work if you have duplicated types in your variant.
CodePudding user response:
As usual, std::index_sequence
might help:
#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)>());
}
Note: for prev
, Is 1
should be replaced by Is sizeof...(Is) - 1
.
CodePudding user response:
The problem here is that while
emplace
expects what clang's error message calls an "explicitly-specified argument",index
does not appear to beconstexpr
.
You can use std::variant
to convert the run-time index
into a compile-time constant, that is, 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()]);
}