У меня есть функция C++, которая выполняет вложенный цикл for с идентичными вычислениями два раза, каждый из вложенных циклов for действует на разные данные и на немного разные индексы массива/цикла, но концептуально делает одно и то же. Это сэкономило бы мне много времени на кодирование/отладку, если бы я просто один раз написал первый вложенный цикл for, а затем заставил компилятор дублировать его, воссоздавая вложенный цикл и заменяя соответствующие переменные и индексы. Вот фиктивный пример.
void function(myStruct &b){
// first nested for loop
for(int i=1; i < 10; i++){
for(int j=0; j < 9; j++){
b.iF[i,j] = b.someOtherArray[i-1,j]*2;
}
}
// second nested for loop I do not want to code
for(int i=0; i < 9; i++){
for(int j=1; j < 10; j++){
b.jF[i,j] = b.someOtherArray[i,j-1]*2;
}
}
}
Второй вложенный цикл for аналогичен первому, но «b.iF-->b.jF», «i-1,j-->i,j-1» и так далее. В остальном они делают то же самое. Я никогда не работал с шаблонами, но это не похоже на правильное использование шаблонов, поскольку нет разницы в типах. А определение макросов потребует записи «мяса» вложенных циклов в виде макроса, который будет не очень читабельным. Не уверен, что это правильное прочтение, или если есть что-то, о чем я не знаю, это было бы хорошим решением.
Я мог бы выделить каждый вложенный цикл в отдельную функцию и вызывать их с правильными переменными и индексами в качестве аргументов, но я хочу попробовать что-то другое.
Реальный вариант использования — это переход от одномерной физической задачи к трехмерной задаче, где я повторяю одномерную физику еще два раза в двух других пространственных измерениях. Так что приятно просто написать одномерный случай и попросить компилятор повторить это для меня для двух других пространственных измерений.
В зависимости от того, как вы собираетесь его использовать, могут возникнуть проблемы с локальностью кэша при переходе на 3D. Это не будет проблемой для массивов небольших размеров, которые поместятся в кеш. Кстати, почему вы не можете создать все массивы [10,10] и просто игнорировать ненужные строки/столбцы? В больших массивах ваш код должен поддерживать кэш, иначе он будет работать медленно!
«поскольку нет разницы типов». Шаблон также работает со значением, как «указатель метода».
Кажется, у вас есть внешние доступы...
... и синтаксические ошибки.
Я вижу два способа сделать это, используя абстракции более высокого уровня.
Во-первых, сами циклы можно превратить в диапазоны. Во-вторых, операцию можно превратить в процедуру.
void function(myStruct& b){
auto body = [&](int i0, int j0, int i1, int j1) {
b.iF[i0,j0] = b.someOtherArray[i1,j1]*2;
};
auto range1 = range(0, 10);
auto range2 = range(0, 9);
(range1 * range2)([&](auto i, auto j){
body(i,j,i-1,j);
});
(range2 * range1)([&](auto i, auto j){
body(i,j,i,j-1);
});
}
Здесь у меня есть типы диапазонов, которые допускают операции с ними и принимают функциональные объекты. Это попытка удалить структурное кодирование (циклы for, сохранение конечных элементов и т. д.) и превратить его в данные.
Я переписываю тело так, чтобы входные и выходные аргументы были разными. Затем я пишу лямбда-обертку, которая принимает решения. Вместо этого вы можете взять i,j, delta_i и delta_j.
body(i,j,0,-1);
Конечной целью будет код, который компилируется так же, как ваши рукописные циклы.
Конечно, range
можно просто завернуть std::views::iota
и operator*
можно просто завернуть std::views::cartesian_product
.
Я не рассматривал лямбды. Хотя на данный момент он фактически просто разбивается на отдельные функции. Спасибо!
@Kschau Вроде того; обе лямбды имеют захват [&] для неизменяющихся частей: вы помещаете изменяющиеся части в вызов функции, что одновременно документирует вещи и очищает шаблон. Они также имеют локальную область действия, поэтому вы знаете, что они вызываются только локально, что упрощает рассуждения об их работе/правильности. Наконец, их можно позиционировать в логике, которая их использует, а не быть абстрактной внешней вещью, что может облегчить читаемость. Помните «Тьюринговую яму»: если у вас есть if и goto, все программные конструкции становятся просто сахаром, но этот сахар имеет значение.
Я мог бы выделить каждый вложенный цикл в отдельную функцию и вызывать их с правильными переменными и индексами в качестве аргументов. Это мое предложение. помещение общего кода в какую-либо функцию позволяет повторно использовать код с разными переменными.