C++ Error/exception handling I know the following code are horrible, so I'd like a review. Criticism are welcome. I want to know what went wrong, and how I can avoid the same mistake in the future.

Version 1
const unsigned int set_max {32};
std::vector&lt;unsigned int&gt; base;
base.reserve(argc-1);
for (int i = 1; i &lt; argc; ++i) {
  unsigned long tmp;
  try { tmp = std::stoul(argv[i], nullptr, 10); }
  catch (std::invalid_argument) { std::cerr &lt;&lt; &quot;Invalid base! Conversion failed.\n&quot;; return -2; }
  catch (std::out_of_range) { std::cerr &lt;&lt; &quot;Invalid base! Too large.\n&quot;; return -2; }
  if (tmp &gt; std::numeric_limits&lt;unsigned int&gt;::max()) { std::cerr &lt;&lt; &quot;Invalid base! Too large.\n&quot;; return -2; }
  if (tmp &lt; 2) { std::cerr &lt;&lt; &quot;Invalid base! Must at least be 2.\n&quot;; return -2; }
  if (tmp &gt; set_max) { std::cerr &lt;&lt; &quot;Invalid base! Must be within &quot; &lt;&lt; set_max &lt;&lt; &quot;.\n&quot;; return -2; }
  base.push_back(static_cast&lt;unsigned int&gt;(tmp));
}


Version 2
const int set_max {32};
std::vector&lt;unsigned int&gt; base;
base.reserve(argc-1);
try { for (int i = 1; i &lt; argc; ++i) {
  unsigned long tmp; try { tmp = std::stoul(argv[i], nullptr, 0); }
  catch (std::invalid_argument                         ) { throw std::string(&quot;Conversion failed&quot;                        ); }
  catch (std::out_of_range                             ) { throw std::string(&quot;Too large&quot;                                ); }
  if    (tmp &gt; std::numeric_limits&lt;unsigned int&gt;::max()) { throw std::string(&quot;Too large&quot;                                ); }
  if    (tmp &lt; 2                                       ) { throw std::string(&quot;Must at least be 2&quot;                       ); }
  if    (tmp &gt; set_max                                 ) { throw std::string(&quot;Must be within &quot; + std::to_string(set_max)); }
  base.push_back(static_cast&lt;unsigned int&gt;(tmp));
} } catch (std::string &amp;err_msg) { std::cerr &lt;&lt; &quot;Invalid base! &quot; &lt;&lt; err_msg &lt;&lt; &quot;.\n&quot;; return -2; }


Version 3
const int set_max {32};
std::vector&lt;unsigned int&gt; base;
base.reserve(argc-1);
for (int i = 1; i &lt; argc; ++i) {
  unsigned long tmp;
  try {
    try {
      tmp = std::stoul(argv[i], nullptr, 0);
    }
    catch (std::invalid_argument) {
      throw std::string(&quot;Conversion failed&quot;);
    }
    catch (std::out_of_range) {
      throw std::string(&quot;Too large&quot;);
    }
    if (tmp &gt; std::numeric_limits&lt;unsigned int&gt;::max()) {
      throw std::string(&quot;Too large&quot;);
    }
    if (tmp &lt; 2) {
      throw std::string(&quot;Must at least be 2&quot;);
    }
    if (tmp &gt; set_max) {
      throw std::string(&quot;Must be within &quot; + std::to_string(set_max));
    }
  }
  catch (std::string &amp;err_msg) {
    std::cerr &lt;&lt; &quot;Invalid base! &quot; &lt;&lt; err_msg &lt;&lt; &quot;.\n&quot;;
    return -2;
  }
  base.push_back(static_cast&lt;unsigned int&gt;(tmp));
}
Readability

Version 1:
This is unreadable. If you presented that me in a professional context I would literally fire you (for real).

Version 2:
This is arguably readable. But you are making it hard for yourself and other people. You are probably going to be breaking your companies coding guidelines and I can see your colleagues complain. But its all subjective. I would not like you if we were working on the same code base.

Version 3:
This is a reasonable readable version.


Code Review
Your use of exceptions is a bit much. Exceptions are great when you can not correct the problem at the point of the error and need to transfer control to a higher level context and thus can take a reasonable action based on context.
Here you are throwing exceptions and catching them in the same function (i.e you are not moving to a higher context). It's not terrible, but you can achieve the same effect with error codes (and the error codes do not cross an interface boundary so are OK). I am not going to argue for error codes or against them. In this case that is a style thing and how your team likes to handle the situation.
The only issue I have is that exception that are throw can potentially change over time, and you don't catch the any (catch (...)) and thus can potentially have an accidental application termination with no error message.
I don't know how many arguments this takes. But it exits on the first parameter error. You may want to check all the parameters and generate more errors (if possible). This potentially will allow the user to fix all the parameters at once rather than having to run the application multiple times fixing one parameter at a time.