C++:如何用单个命令替换复杂的迭代?

C++: How to replace a complex iteration with a single command?

本文关键字:复杂 迭代 替换 命令 何用单 C++      更新时间:2023-10-16

问题

我有时会有复杂的迭代过程,必须在代码中重复几次,但在每次迭代中执行的表达式在代码的不同位置都不同。必须在任何地方重写迭代过程是丑陋的,并且容易出错。如何包装此迭代过程?

例如,考虑这个相对复杂的迭代

std::string bitmask(K, 1); // K leading 1's
bitmask.resize(N, 0); // N-K trailing 0's
// print integers and permute bitmask
do {
// Loop through BIG and SMALL indices
for (size_t BIGindex = 0; BIGindex < nbBigs; ++BIGindex)
{
size_t nbSmalls;
if (BIGindex == nbBigs)
{
nbSmalls = nbSmallsOfLastBig;
} else
{
nbSmalls = nbSmallsStandard;
}
for (size_t SMALLindex = 0; SMALLindex < nbSmalls; ++SMALLindex)
{
// doStuff with bitmask, BIGindex and SMALLindex
}        
}
} while (std::prev_permutation(bitmask.begin(), bitmask.end()));

如何定义一个命令/别名(因为缺乏更好的词(,例如"doComplexIteration",它将所有这些迭代包装成一个更简单的命令。类似的东西

doComplexIteration
{
// doStuff with bitmask, BIGindex and SMALLindex       
}

一个不完全令人满意的解决方案

一种方法是将要完成的事情包装在函数中,例如

void doComplexIterationOnFunction(void (*doStuff)(std::string bitmask, size_t BIGindex, size_t SMALLindex))
{
std::string bitmask(K, 1); // K leading 1's
bitmask.resize(N, 0); // N-K trailing 0's
// print integers and permute bitmask
do {
// Loop through BIG and SMALL indices
for (size_t BIGindex = 0; BIGindex < nbBigs; ++BIGindex)
{
size_t nbSmalls;
if (BIGindex == nbBigs)
{
nbSmalls = nbSmallsOfLastBig;
} else
{
nbSmalls = nbSmallsStandard;
}
for (size_t SMALLindex = 0; SMALLindex < nbSmalls; ++SMALLindex)
{
(*doStuff)(bitmask, BIGindex, SMALLindex);
}        
}
} while (std::prev_permutation(bitmask.begin(), bitmask.end()));
}

然后调用它为

doComplexIterationOnFunction(doSpecificStuff);

但是,它迫使我系统地将我希望在每次迭代中执行的任何代码包装在一个函数中,这有点麻烦和有点愚蠢,因为有时代码很短。

无需使用函数指针,您只需将函数设置为模板类型,然后允许您在调用站点传递 lambda。 那看起来像

temaplte<typename Function>
void doComplexIterationOnFunction(Function doStuff)
{
std::string bitmask(K, 1); // K leading 1's
bitmask.resize(N, 0); // N-K trailing 0's
// print integers and permute bitmask
do {
// Loop through BIG and SMALL indices
for (size_t BIGindex = 0; BIGindex < nbBigs; ++BIGindex)
{
size_t nbSmalls;
if (BIGindex == nbBigs)
{
nbSmalls = nbSmallsOfLastBig;
} else
{
nbSmalls = nbSmallsStandard;
}
for (size_t SMALLindex = 0; SMALLindex < nbSmalls; ++SMALLindex)
{
std::invoke(doStuff, bitmask, BIGindex, SMALLindex);
}        
}
} while (std::prev_permutation(bitmask.begin(), bitmask.end()));
}

然后你会这样称呼它

doComplexIterationOnFunction(doSpecificStuffFunction) // pass function
doComplexIterationOnFunction(doSpecificStuffFuntor) // pass functor
doComplexIterationOnFunction([](auto foo, auto bar, auto baz) { return foo + bar - baz; }) // pass lambda

有一个不同的选项:

反转控件,方法是编写一个范围,或者至少足够多的范围来使用 range-for:

struct ComplexIterationRange {
static constexpr auto end() noexcept { struct {} r; return r; }
static auto begin() {
struct {
std::string bitmask;
std::size_t SMALLindex = 0, BIGindex = 0;
const auto& operator*() const noexcept { return *this; }
auto& operator++() noexcept {
if (++SMALLindex >= nbSmallsStandard) {
if (++BIGindex >= nbBigs) {
if (!std::prev_permutation(bitmask.begin(), bitmask.end()))
return *this;
BIGindex = 0;
}
SMALLindex = 0;
}
return *this;
}
bool operator!=(decltype(end())) const noexcept {
return SMALLindex < nbSmallsStandard || BIGindex < nbBigs;
}
} r { []{ std::string r(K, 1); r.resize(N, 0); return r; }() };
return r;
}
};

像这样使用它:

for (auto&& x : ComplexIterationRange()) {
Use x.SMALLindex, x.BIGindex, and x.bitmask here
...
}

与将函数指针甚至 lambda 传递给模板函数相比,它的优势在于调用方具有更大的灵活性和控制力。
代价是在编写范围时将您的大脑打结。