我有一个类型为int
或std::set<int>
的std::variant
元素的std::vector
。如果迭代元素的类型是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
不再是它的第二选择,那么出于某种原因,上述所有逻辑将再次中断。
您应该考虑使用std::holds_alternative
而不是index()
,并且使用带有std::get
的类型而不是索引,它将自动适应这种变化。
如果迭代元素是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]);
过滤可以通过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_LIFT
和boost::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));
}
这都是关于你在标题中提到的过滤。然而,在问题的正文中,您实际上并不是在进行过滤,而是在做其他事情,因为您在遍历它时修改了集合。
正如已经指出的那样,您在std::variant
上调用insert
,而不是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 );
}
}
}
这允许您测试类型并使用包含的替代。