Are these try/catch blocks cluttering the code?
Your convert function should be pure.

convert should be doing one thing and one thing only, and that is to convert the given string to int. All those checks you do are not useful because they can easily be performed in the function that calls convert.

int convert(const std::string&amp; text) {
    return std::stoi(text);
}


Your convert function is not needed.

Seeing as after changing it to a pure function, it is now acting as a wrapper to another function call, you don't need it anymore, and it can now be replaced by the function it calls, std::stoi.

Move convert logic elsewhere

Move all the logic from within the convert method to your overloaded function, std::istream&amp; operator &gt;&gt; (std::istream&amp; is, Grid_row&amp; grid_row).

std::istream&amp; operator &gt;&gt; (std::istream&amp; is, Grid_row&amp; grid_row) {
    std::size_t fin;
    int val;

    for (std::string term; is &gt;&gt; term; ) {
        try {
            val = std::stoi(term, &amp;fin);
            if (fin != term.size()) {
                std::cerr &lt;&lt; "Could not fully convert " + text + " to integer. Value was converted to " + text.substr(0, fin) + "." &lt;&lt; '\n';
            }
        } catch(const std::exception&amp; e) {
            std::cerr &lt;&lt; "Due to error: \"" + std::string{e.what()} +"\"; Could not convert " + text + " to integer. Value was converted to 0." &lt;&lt; '\n';
        }

        grid_row.push_back(val);

    }
    return is;
}


Now all you have is one try-catch, which is really all that is needed