I have a std::vector
of std::variant
elements with type int
or std::set<int>
. I want to loop over this vector and insert
an extra item if the iterated element is of type std::set<int>
. However, it seems that querying the index at run-time is not allowed. How can I achieve this?
#include <variant>
#include <set>
#include <vector>
int main()
{
using Variants = std::variant<int, std::set<int>>;
std::vector<Variants> var_vec;
var_vec.push_back(999);
std::set<int> a = {0,1,2};
var_vec.push_back(a);
for (int i = 0; i < var_vec.size(); ++i)
{
// if the element var_vec[i] is of type std::set<int>
if (var_vec[i].index() == 1) var_vec[i].insert(888); // !ERROR! How to achieve this?
}
return 0;
}
Error message:
error: '__gnu_cxx::__alloc_traits<std::allocator<std::variant<int, std::set<int, std::less<int>, std::allocator<int> > > >, std::variant<int, std::set<int, std::less<int>, std::allocator<int> > > >::value_type' {aka 'class std::variant<int, std::set<int, std::less<int>, std::allocator<int> > >'} has no member named 'insert'
There's nothing wrong with calling index()
at runtime, this happens all the time. The real problem is:
var_vec[i].insert(888);
var_vec[i]
is a std::variant
. It does not have a method called insert()
.
std::get<1>(var_vec[i]).insert(888);
This gives you the variant's set, which will happily allow you to insert()
something.
One problem with this overall approach is that if you, for some reason, want to modify your variant, and that std::set
is no longer its 2nd alternative, for some reason, all of the above logic will break again.
You should consider using std::holds_alternative
, instead of index()
, and using a type with std::get
instead of an index, which will automatically adapt to this kind of a change.
I want to loop over this vector and insert an extra item if the iterated element is of type std::set. However, it seems that querying the index at run-time is not allowed. How can I achieve this?
You don't need to check the type at runtime, you could just use std::visit()
and check if the variant's type is std::set<int>
at compile-time:
// Others headers go here...
#include <type_traits>
// ...
for (int i = 0; i < var_vec.size(); ++i)
// if the element var_vec[i] is of type std::set<int>
std::visit([](auto&& arg) {
if constexpr (std::is_same_v<std::decay_t<decltype(arg)>, std::set<int>>)
arg.insert(888);
}, var_vec[i]);
The filtering can be done via ranges::views::filter
, and we can use std::visit
to make up a predicate.
To do so we need to feed std::visit
with a predicate on the types. A predicate on types can be made up as a set of overlaoded functions that each takes a type and all return a bool
; you can create it via boost::hana::overload
.
However, to filter the variants we need to feed std::visit
only with its first argument, and to leave its second parameter free; one way to do it is to wrap std::visit
in a nested lambda, like this:
auto visit_with = [](auto const& fs){
return [&fs](auto const& xs) {
return std::visit(fs, xs);
};
};
Another way is to use BOOST_HOF_LIFT
and boost::hana::curry
the result to make it partially applicabale, which is all done in one line:
auto visit_with = curry<2>(BOOST_HOF_LIFT(std::visit));
Here's the complete code:
#include <boost/hana/functional/overload.hpp>
#include <boost/hana/functional/partial.hpp>
#include <iostream>
#include <range/v3/view/filter.hpp>
#include <set>
#include <variant>
#include <vector>
using namespace boost::hana;
using namespace ranges::views;
int main()
{
using Variants = std::variant<int, std::set<int>>;
std::vector<Variants> var_vec;
var_vec.push_back(999);
std::set<int> a = {0,1,2};
var_vec.push_back(a);
// predicate
auto constexpr true_for_sets = overload(
[](std::set<int> const&){ return true; },
[](auto const&){ return false; });
// function object wrapping std::visit
auto visit_with = curry<2>(BOOST_HOF_LIFT(std::visit));
// filter
auto var_vec_out = var_vec | filter(visit_with(true_for_sets));
}
And this is all about the filtering you mention in the title. However, in the body of the question, you're not actually doing a filtering, but something else, as you are modifying the set as you traverse it.
As was already pointed out you're calling insert
on an std::variant
and not the std::set
.
One approach I'd use is using std::get_if
like so:
#include <variant>
#include <set>
#include <vector>
int main( ) {
using Variants = std::variant<int, std::set<int>>;
std::vector<Variants> var_vec;
var_vec.push_back(999);
std::set<int> a = {0,1,2};
var_vec.push_back(a);
for ( auto& v : var_vec ) {
// If the variant does not hold a set, get_if returns a nullptr.
if ( auto set{ std::get_if<std::set<int>>( &v ) } ) {
set->insert( 888 );
}
}
// Or you could use holds_alternative with get, but this isn't as clean.
for ( auto& v : var_vec ) {
if ( std::holds_alternative<std::set<int>>( v ) ) {
auto set{ std::get<std::set<int>>( v ) };
set.insert( 999 );
}
}
}
This allows you to both test for the type and use the contained alternative.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.