2017-09-22 91 views
3

我经常遇到代码中的情况,我想根据运行时情况以直接或相反的顺序遍历范围。这通常导致在如下所示的代码实现升压范围适配器reversed_if

if (reverse) { 
    using boost::adaptors::reversed; 
    for (auto const & x : range | reversed) do_stuff(x); 
} else { 
    for (auto const & x : range) do_stuff(x); 
} 

std::vector<Foo> v(range.begin(), range.end()); 
if (reverse) boost::range::reverse(v); 
for (auto const & x : v) do_stuff(x); 

包含代码重复(第一个)或是低效的(第二个)。

我一直在思考很多次关于假设的升压范围适配器,将有条件地扭转一个范围,这样我就可以写

using boost::adaptors::reversed_if; 
for (auto const & x : range | reversed_if(reverse)) do_stuff(x); 

我可以实现它自己(从here开始),但我不确定如何继续。为了支持运行时条件,恐怕我必须在每次迭代时检查布尔值以确定迭代方向或使用虚拟性来分派迭代代码。这是Boost系列适配器没有提供的原因吗?

任何替代解决方案?

回答

1

如果要避免在每个增量的运行时检查哪条路,您必须将运行时值转换为循环结构外的编译时间值。

在这种情况下,我们希望我们循环的范围有所不同,而身体则不会。

easy这样做的方法是为身体写一个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_switch的Index(std::size_t)作为第一个参数。它返回一个lambda,它接受一个参数列表。它返回一个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)); 
    }; 
    }; 
} 

是实现中的草图(它几乎肯定包含构建错误)。

困难的部分是“类型流动”(投币一词)并不像你通常想要的那样。所以我基本上被迫使用延续传球风格。

请注意,许多编译器对包含整个lambda的包扩展不满意。返回函数指针辅助函数可以写成:在这些编译器

 static const fptr table[] = { 
     get_fptr<decltype(options), decltype(f)>()... 
     }; 

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

然后用替换表。

+0

哇!谢谢!我明白了,但我会尝试了解魔术开关的实施细节。 “decltype(范围)(范围)”的含义是什么?这是一个演员,为什么需要? –

+1

@ChristopheFuzier对于'auto &&'参考,它可以完美转发。阅读它作为“范围作为它声明的类型”。它相当于'std :: forward (range)',但只有'range'是'auto &&'或'T &&'(转发引用)类型的一半长。如果'range'是一个值类型('auto'或'T'),它不起作用。 – Yakk

+0

@ChristopheFuzier另外请注意,'magic_switch'可以基于变体工作。 – Yakk