如何过滤掉std::variant向量中某些数据类型的元素?

How to filter out elements of certain data types in a vector of std::variant?

我有 std::vectorstd::variant 个类型为 intstd::set<int> 的元素。如果迭代元素的类型为 std::set<int>,我想遍历此向量和 insert 一个额外的项目。但是,似乎不允许在 运行 时间查询索引。我怎样才能做到这一点?

#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: '__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'

在运行时调用 index() 没有错,这种情况一直都在发生。真正的问题是:

var_vec[i].insert(888);

var_vec[i] 是一个 std::variant。它没有名为 insert().

的方法
std::get<1>(var_vec[i]).insert(888);

这为您提供了变体集,它很乐意让您insert()做一些事情。

这种整体方法的一个问题是,如果您出于某种原因想要修改您的变体,并且 std::set 不再是其第二个替代方案,出于某种原因,上述所有逻辑都会又断了。

你应该考虑using std::holds_alternative, instead of index(), and using a type with std::get而不是索引,它会自动适应这种变化。

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?

不需要在运行时检查类型,您可以只使用std::visit()并在编译时检查变体的类型是否为std::set<int> -时间:

// 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]);

正如已经指出的那样,您在 std::variant 而不是 std::set 上调用 insert

我使用的一种方法是使用 std::get_if,如下所示:

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

这允许您测试类型并使用包含的替代项。

过滤可以通过ranges::views::filter来完成,我们可以用std::visit来组成谓词。

为此,我们需要为 std::visit 提供关于类型的谓词。类型上的谓词可以由一组重叠的函数组成,每个函数都接受一个类型,并且所有 return a bool;您可以通过 boost::hana::overload.

创建它

但是,要过滤变体,我们只需要向 std::visit 提供第一个参数,并保留其第二个参数;一种方法是将 std::visit 包裹在嵌套的 lambda 中,如下所示:

    auto visit_with = [](auto const& fs){
      return [&fs](auto const& xs) {
        return std::visit(fs, xs);
      };
    };

另一种方法是使用 BOOST_HOF_LIFTboost::hana::curry 结果使其部分适用,这全部在一行中完成:

    auto visit_with = curry<2>(BOOST_HOF_LIFT(std::visit));

完整代码如下:

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

这就是您在标题中提到的过滤。但是,在问题的 body 中,您实际上并不是在进行过滤,而是在进行其他操作,因为您在遍历集合时正在对其进行修改。