【问题标题】:How to filter out elements of certain data types in a vector of std::variant?如何在 std::variant 的向量中过滤掉某些数据类型的元素?
【发布时间】:2021-08-13 12:30:18
【问题描述】:

我有一个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'

【问题讨论】:

标签: c++ c++17 stdvector stdset std-variant


【解决方案1】:

在运行时调用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,而不是index(),以及using a type with std::get,而不是索引,它会自动适应这种变化。

【讨论】:

  • 您能否通过修改我提供的示例代码来展示如何使用std::holds_alternativestd::get() 的示例?谢了。
  • 只需使用std::holds_alternative&lt;std::set&lt;int&gt;&gt;()std::get&lt;std::set&lt;int&gt;&gt;()。这在我提供的链接中得到了充分的解释,有很多例子,每本高级 C++ 教科书中都应该有更多的例子。链接的参考文档或教科书中是否有关于这个主题不清楚的具体内容?
【解决方案2】:

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

需要在运行时检查类型,您可以使用std::visit() 并在编译时检查变体的类型是否为std::set&lt;int&gt;

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

【讨论】:

    【解决方案3】:

    过滤可以通过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));
    }
    

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

    【讨论】:

      【解决方案4】:

      正如已经指出的那样,您在 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 );
              }
          }
      }
      

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

      【讨论】:

        猜你喜欢
        • 2022-06-14
        • 1970-01-01
        • 2021-03-25
        • 2016-08-02
        • 2014-08-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多