cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

Сделать break N/ continue N для выхода из/продолжения внешних циклов. #520

Closed topin89 closed 2 years ago

topin89 commented 2 years ago

Тема выхода/продолжения из внешних циклов довольно древняя, и разные языки решали её по разному. В C/C++ её чаще всего решают через goto

for(int x=0; x < width; ++x){
    for(int y=0; x < heigh; ++y){
        if(img[x][y] != 0 ){
            goto img_processed;
        }
    }
}
img_processed:

for(int x=0; x < width; ++x){
    for(int y=0; x < heigh; ++y){
        if(img2[x][y] != 0 ){goto column_processed;}
    }
column_processed:
}

А также вынося вложенные циклы в отдельную функцию/лямбду и делая return для выхода из вложенных циклов. Основная проблема с goto даже не с читаемостью, а с тем, что можно легко забыть про метки наружу цикла и сами метки должны быть уникальными, что неудобно при копипасте (вопрос о пользе и вреде копипасты не рассматриваем).

В других языках не лучше, где-то это специальные метки для break, как в Java или break varname в Visual Basic. Где-то просто никак, например в Python.

И есть баш.

for folder in folder1 folder2 folder3; do
    for file in $folder/*; do
        if  [ -c $file ]; then
            echo "found char device $file"
            break 2  # <---
        fi
    done
done

Абсолютно простое решение проблемы. Хотим указать, что нам выйти из двух вложенных циклов -- берём и указываем. Заодно решается вопрос, как выйти из цикла внутри switch.

В нашем случае это выглядело бы так:

for(int x=0; x < width; ++x){
    for(int y=0; x < heigh; ++y){
        if(img[x][y] != 0 ){
            break 2;
        }
    }
}

for(int x=0; x < width; ++x){
    for(int y=0; x < heigh; ++y){
        if(img2[x][y] != 0 ){continue 2;}
    }
}

Примеров применения множество. Выход из циклов внутри switch. Выход из while, в котором мы ждём прихода массива и если нужный элемент найден покидаем ожидание. Если вбить в гугле c++ outer loop break, найдётся 31 миллион результатов. 4 миллиона в Яндексе.

При этом по стандарту после break и continue не может быть никаких символов кроме ; и пробелов, шансов нарушить обратную совместимость очень мало

pavelkryukov commented 2 years ago
for(int x=0; x < width; ++x){
    for(int y=0; x < heigh; ++y){
        if(img[x][y] != 0 ){
            break 2;
        }
    }
}

Это хуже, чем goto, потому что требует огромной внимательности от того, кому нужно будет добавить третий цикл:

for(int x=0; x < width; ++x){
    for(int z=0; z < depth; ++z){
       for(int y=0; x < heigh; ++y){
           if(img[x][y] != 0 ){
               break 2; // oops!
            }
        }
    }
}

А также вынося вложенные циклы в отдельную функцию/лямбду и делая return для выхода из вложенных циклов.

Обычно от этого код становится только понятнее и масштабируемее :-)

Ещё можно подумать, как для такого обхода использовать корутины.

Izaron commented 2 years ago

Во многих философских трудах пишут, что многовложенные циклы это code smell, которого нужно избегать. Чаще всего рефакторят тело цикла в отдельные функции.

В таком случае вопрос странсформируется в "как мне выпрыгнуть на N функций вверх", это уже точно никому не должно быть нужно.

topin89 commented 2 years ago

Это хуже, чем goto, потому что требует огромной внимательности от того, кому нужно будет добавить третий цикл

Хороший аргумент, согласен.