C++ 如果需要,实施增压范围适配器
我经常在代码中遇到这样的情况,即我希望根据运行时的条件以直接或相反的顺序迭代某个范围。这通常会产生如下代码C++ 如果需要,实施增压范围适配器,c++,boost,foreach,boost-range,boost-adaptors,C++,Boost,Foreach,Boost Range,Boost Adaptors,我经常在代码中遇到这样的情况,即我希望根据运行时的条件以直接或相反的顺序迭代某个范围。这通常会产生如下代码 if (reverse) { using boost::adaptors::reversed; for (auto const & x : range | reversed) do_stuff(x); } else { for (auto const & x : range) do_stuff(x); } 或 我可以自己实现它(从开始),但我不确定
if (reverse) {
using boost::adaptors::reversed;
for (auto const & x : range | reversed) do_stuff(x);
} else {
for (auto const & x : range) do_stuff(x);
}
或
我可以自己实现它(从开始),但我不确定如何继续。为了支持运行时条件,恐怕我必须在每次迭代时检查一个布尔值来确定迭代方向,或者使用虚拟性来调度迭代代码。这就是为什么增压范围适配器中不提供此功能的原因吗
是否有其他解决方案?如果要避免在每次增量时进行运行时检查,则必须将运行时值转换为循环结构之外的编译时值 在这种情况下,我们希望循环的范围发生变化,而实体不发生变化 实现这一点的简单方法是为主体编写lambda,然后使用开关选择要选择的循环
auto do_stuff = [&](auto&& elem){ /* code */ };
if (reverse) {
using boost::adaptors::reversed;
for (auto const & x : range | reversed) do_stuff(x);
} else {
for (auto const & x : range) do_stuff(x);
}
我们在循环之外完成了运行时调度,创建了两个不同的循环,其中包含关于它们如何循环的静态类型信息
我们可以制作如下适配器:
magic_switch
( reverse )
( range, range|reversed )
(
[&](auto&& range){
for (auto const& x : decltype(range)(range)) {
do_stuff(x);
}
}
);
其中,magic\u switch
将索引(std::size\t
)作为其第一个参数。它返回一个lambda,它接受一个参数列表。它返回一个lambda,它接受一个lambda并将第二个列表中的参数传递给它,该参数由第一个参数在该列表中的索引确定
inline auto magic_switch( std::size_t I ) {
return [I](auto&&...options) {
return [I, &](auto&& f)->decltype(auto) {
using fptr = void(*)(void const volatile* op, decltype(f));
static const fptr table[] = {
+[](void const volatile* op_in, decltype(f) f) {
auto* option = static_cast<std::decay_t<decltype(options)>*>(op_in);
decltype(f)(f)( decltype(options)(*option) );
}...
};
const volatile void* ptrs[] = {std::addressof(options)...};
if (I >= sizeof...(options)) I = sizeof...(options)-1;
if (I == -1) return;
table[I]( ptrs[I], decltype(f)(f) );
};
};
}
内联自动魔术开关(标准::大小\u t I){
返回[I](自动&&…选项){
返回[I,&](自动)&&f)->decltype(自动){
使用fptr=void(*)(void const volatile*op,decltype(f));
静态常数fptr表[]={
+[](void const volatile*op_in,decltype(f)f){
自动*选项=静态投切(投切);
decltype(f)(f)(decltype(options)(*option));
}...
};
常量volatile void*ptrs[]={std::addressof(选项)…};
如果(I>=sizeof…(选项))I=sizeof…(选项)-1;
如果(I==-1)返回;
表[I](ptrs[I],decltype(f)(f));
};
};
}
是实现的草图(几乎肯定包含构建错误)
困难的部分是“类型流”(创造一个术语)没有按照你通常希望的方式进行。所以我不得不基本上采用连续传球的方式
请注意,许多编译器对包含整个lambda的包扩展不满意。可以编写返回函数指针的帮助函数:
template<class F>
using f_ptr = void(*)(const volatile void*, F&&);
template<class Option, class F>
f_ptr<F> get_f_ptr() {
return +[](void const volatile* op_in, F&& f) {
auto* option = static_cast<std::decay_t<Option>*>(op_in);
std::forward<F>(f)( std::forward<Option>(*option) );
};
}
模板
使用f_ptr=void(*)(常数volatile void*,f&&);
模板
f_ptr get_f_ptr(){
return+[](void const volatile*op_in,F&&F){
自动*选项=静态投切(投切);
标准::转发(f)(标准::转发(*选项));
};
}
然后将表格替换为:
static const fptr table[] = {
get_fptr<decltype(options), decltype(f)>()...
};
static const fptr table[]{
获取\u fptr()。。。
};
在那些编译器上。哇!非常感谢。我明白你的想法,但我会尽力理解你的魔术开关的实现细节。
decltype(range)(range)
的意义是什么?这是一个演员阵容吗?为什么需要它?@ChristopherFuzier提供了一个自动&
参考,可以完美地转发。将其读取为“范围与声明的类型相同”。它相当于std::forward(range)
,但只要range
是auto&
或T&
(转发参考)类型,它的长度就只有std::forward(range)的一半。如果range
是一种值类型(auto
或T
),则它不起作用。@Christopher还注意到magic_开关可以基于variant
工作。
template<class F>
using f_ptr = void(*)(const volatile void*, F&&);
template<class Option, class F>
f_ptr<F> get_f_ptr() {
return +[](void const volatile* op_in, F&& f) {
auto* option = static_cast<std::decay_t<Option>*>(op_in);
std::forward<F>(f)( std::forward<Option>(*option) );
};
}
static const fptr table[] = {
get_fptr<decltype(options), decltype(f)>()...
};