0

I am trying to iterate over filtered (by type) std::vector<std::variant<T...>>. Problem I hit is that the std::get is overloaded function(and has many implicit template args I need to type out) so I would need to cast it to something specific.

This seems ugly and hard so I tried to hack together holds_alternative and get into a functor that returns a view of filter( for holds_alternative) and transform( for get) views. That does not work.

Questions:

  • can I do what I want without providing my structs and without ugly casts needed to specify std::get overload, ugly need to specify all template arguments to std::holds_alternative, std::get?
  • if answer to previous quesion is no: why does my attempt does not work

note:

my code does not attempt to handle properly const and && like std::get does, I am fine with it working only on const l-values.

My code:

#include <algorithm>
#include <iostream>
#include <ranges>
#include <variant>
#include <vector>

struct Red {
  int x = 0;
};

struct Green {
  int x = 1;
};

struct Blue {
  int x = 2;
};

using Color = std::variant<Red, Green, Blue>;

template <typename T> struct engaged_t {
  template <typename... Ts>
  constexpr bool operator()(const std::variant<Ts...> &variant) {
    return std::holds_alternative<T>(variant);
  }
};

template <typename T> inline constexpr auto engaged = engaged_t<T>{};

template <typename T> struct variant_get_t {
  template <typename... Ts>
  constexpr decltype(auto) operator()(std::variant<Ts...> &variant) {
    return std::get<T>(variant);
  }
};

template <typename T> inline constexpr auto variant_get = variant_get_t<T>{};

template <typename T> struct variant_filter_t {};

template <typename T>
inline constexpr auto variant_filter = variant_filter_t<T>{};

template <typename R, typename T>
decltype(auto) operator|(const R &r, variant_filter_t<T>) {
  return r | std::views::filter(engaged<T>) |
         std::views::transform(variant_get<T>);
}

int main() {
  std::vector<Color> colors{Red{}, Green{}, Blue{}, Green{}};
  // does not compile
  // for (const auto green: colors | variant_filter<Green>){
  //   std::cout << std::get<Green>(green).x << std::endl;
  // }

  // kaboom if we try to do anything, just to show we can pipe colors to
  // variant_get
  for (const auto bad_green : colors |
                                  std::views::transform(variant_get<Green>) |
                                  std::views::take(0)) {
  }
  // we can also pipe colors to engaged
  for (const auto bluev : colors | std::views::filter(engaged<Blue>)) {
    std::cout << std::get<Blue>(bluev).x << std::endl;
  }
  // and if we manually doe what variant_filter should_do that also seems to
  // work
  for (const auto blue : colors | std::views::filter(engaged<Blue>) |
                             std::views::transform(variant_get<Blue>)) {
    std::cout << blue.x << std::endl;
  }
}
康桓瑋
  • 33,481
  • 5
  • 40
  • 90
NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277
  • So you want a *functor* to have `filter` followed by the `transform` view? – Jarod42 Sep 13 '21 at 14:19
  • 2
    This is a lot of questions. Which one do you want answered? – Barry Sep 13 '21 at 14:20
  • @Jarod42 I want to have a way to compose | std::views::filter(engaged) | std::views::transform(variant_get) without need to type twice. – NoSenseEtAl Sep 13 '21 at 14:21
  • @Barry: well if my assumption is correct I can not nicely write what I want without using custom structs I would like to know why functor does not work since when I do it "manually" it seems to work. – NoSenseEtAl Sep 13 '21 at 14:22
  • @Barry by manual I mean code after this comment in code : and if we manually doe what variant_filter should_do that – NoSenseEtAl Sep 13 '21 at 14:24
  • 2
    You missed a `const` in `variant_get_t::operator()`, on the parameter – StoryTeller - Unslander Monica Sep 13 '21 at 14:25
  • @StoryTeller-UnslanderMonica ah, I never ever considered checking for stuff like that, I presumed I messed up some obscure ranges requirement... you can make it an answer and I will accept. – NoSenseEtAl Sep 13 '21 at 14:31

1 Answers1

2

By changing const R& to forwarding reference R&& (and fixing typo), it works:

template <typename R, typename T>
decltype(auto) operator|(R&& r, variant_filter_t<T>) {
  return r | std::views::filter(engaged<T>)
           | std::views::transform(variant_get<T>);
}

int main() {
  std::vector<Color> colors{Red{}, Green{}, Blue{}, Green{}};
  for (const auto green: colors | variant_filter<Green>){
     std::cout << green.x << std::endl;
  }
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302