2

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++.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
nonchip
  • 1,084
  • 1
  • 18
  • 36

4 Answers4

5

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

Demo

Note: for prev, Is + 1 should be replaced by Is + sizeof...(Is) - 1.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • that works perfectly, and i even understand why, though i would've never figured it out on my own, thanks! :D guess i'll need to learn way more about the new fancy metamagics. – nonchip Oct 22 '21 at 09:13
  • about your note: shouldn't `(Is+sizeof...(Is)-1)%sizeof...(Is)` be the same as `(Is-1)%sizeof...(Is)`? or is modulo being weird about negative numbers? i keep forgetting its behaviour there, i just know it's never the way i expect. – nonchip Oct 22 '21 at 09:28
  • @nonchip The `%` isn't needed in Jarods42's `prev`. Just `Is + sizeof...(Is) - 1`. No it's not the same. `Is - 1` will "underflow" and be `::max()` when `Is` is `0`. – Ted Lyngmo Oct 22 '21 at 09:32
  • 1
    @nonchip: `-1 % 5` is `-1`, but here we use unsigned numbers, so `std::size_t(-1)` is the biggest number, `std::size_t(-1) % N` is not `N - 1` for all `N` – Jarod42 Oct 22 '21 at 09:32
  • @TedLyngmo: `%` is not needed for 0, but it would be for other numbers `2 + (N - 1)` is `N + 1` instead of expected `1`. – Jarod42 Oct 22 '21 at 09:35
  • @Jarod42 oh yeah obviously size_t is unsigned, didn't think of that :D – nonchip Oct 22 '21 at 09:41
  • @Jarod42 True - My brain was stuck on `0` ... as it often is. – Ted Lyngmo Oct 22 '21 at 09:43
  • @Jarod42 Perhaps it should be a separate question, but, is the unary `+` on the lambda needed? It seems the conversion to `void (*)(std::variant&)` happens automatically when assigning the lambdas to `funcs[]`? – Ted Lyngmo Oct 22 '21 at 09:50
  • 1
    @TedLyngmo: Should not be needed, but I prefer to be *"explicit"*. – Jarod42 Oct 22 '21 at 09:51
5

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) {
    // 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);
}

Unlike @Jarod42 solution, this solution will not work if you have duplicated types in your variant.

Holt
  • 36,600
  • 7
  • 92
  • 139
2

This is one of the many situations that calls for index-based visitation. We can write that generically using 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));
    });
}

This passes both the index (which is some integral constant) and the element into the function. And now we can just write:

template <typename... Args>
void next_alt(std::variant<Args...>& v) {
    visit_with_index([&](auto I, auto&&){
        v.emplace<(I+1) % sizeof...(Args)>();
    }, v);
}

As a bonus, mp_with_index is a switch so it'll have better performance than std::visit, so it's a good solution there anyway. Note that this doesn't handle valueless_by_exception, but that's straightforward to add on top if desired.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    For the "bonus" part, both libstdc++ and MSVC's `visit` use a `switch` for sufficiently small variants. – T.C. Oct 23 '21 at 06:16
0

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.

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

Demo.

康桓瑋
  • 33,481
  • 5
  • 40
  • 90