do
{
line.erase(i, 1); // удаляет из строки символ с индексом i
} while (isalpha(line[i]) != 0);
Вот этот кусок очень нехороший.
Во первых в отличие от while(...){}, do{}while (...) делает проверку после того как исполнит свое содержимое. Поэтому, например, первому символу в стоке в любом случае не жить, не важно буква это или нет. Вам нужен обычный while.
Далее, каждый раз когда вы вызываете line.erase(i, 1); Это неизбежно удаляет символ из строки и уменьшает ее длину. Но вы запомнили длину строчки, когда только прочитали ее в переменной size. Поэтому цикл for будет бежать по тем элементам которых давно уже нет. Избавьтесь от переменной size, сравнивайте i напрямую с line.length().
Вместо isalpha(line[i]) != 0 лучше писать просто !isalpha(line[i]).
Едем дальше. Если вы хотите сохранить пробелы, то вам надо удалять текущий символ если он не буква
и не пробел. Условие дословно переводим на C++: (!isalpha(line[i]) && line[i] != ' ')
И еще не совсем ошибка, но если символ не в верхнем регистре, то не обязательно он в нижнем. Например это может быть пробел. К счастью tolower и tщupper ничего не делают с небуквенными символами.