2

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

#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'
4

4 回答 4

5

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

var_vec[i].insert(888);

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

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

这为您提供了变体的集合,这将很乐意让您获得insert()一些东西。

这种整体方法的一个问题是,如果您出于某种原因想要修改您的变体,并且这std::set不再是它的第二种选择,由于某种原因,上述所有逻辑都会再次中断。

您应该考虑使用std::holds_alternative, 而不是index(),并使用类型 withstd::get而不是索引,这将自动适应这种变化。

于 2021-08-13T12:33:55.117 回答
3

如果迭代的元素是 std::set 类型,我想循环这个向量并插入一个额外的项目。但是,似乎不允许在运行时查询索引。我怎样才能做到这一点?

您不需要运行时检查类型,您可以使用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]);
于 2021-08-13T12:42:04.550 回答
2

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

为此,我们需要std::visit使用类型的谓词来提供数据。类型的谓词可以由一组重载函数组成,每个函数都接受一个类型并且都返回一个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));
}

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

于 2021-08-13T13:02:48.013 回答
1

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

我会使用的一种方法是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 );
        }
    }
}

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

于 2021-08-13T12:44:32.043 回答