Update cxxopts from 3.0 to 3.1.1

!android-nn-driver:9431

Signed-off-by: Jim Flynn <jim.flynn@arm.com>
Change-Id: I58143445b5c5cf2aafd0838156c9543adce21e6a
diff --git a/shim/sl/canonical/DriverOptions.cpp b/shim/sl/canonical/DriverOptions.cpp
index 5c73edf..b23dd06 100644
--- a/shim/sl/canonical/DriverOptions.cpp
+++ b/shim/sl/canonical/DriverOptions.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2022 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
@@ -106,7 +106,7 @@
     string optionsAsString(rawEnv);
     regex whiteSpaceRegex("\\s+");
     // Tokienize the string based on whitespace.
-    sregex_token_iterator iter(optionsAsString.begin(), optionsAsString.end(), whiteSpaceRegex, -1);  
+    sregex_token_iterator iter(optionsAsString.begin(), optionsAsString.end(), whiteSpaceRegex, -1);
     sregex_token_iterator end;
     vector<string> cliAsVector(iter, end);
     // As we're pretending to be a command line, argv[0] should be an executable name.
@@ -216,7 +216,7 @@
     {
         cxxopts::ParseResult result = optionsDesc.parse(argc, argv);
     }
-    catch (const cxxopts::OptionException& e)
+    catch (const cxxopts::exceptions::exception& e)
     {
         VLOG(DRIVER) << "An exception occurred attempting to parse program options: " << e.what();
         std::cout << optionsDesc.help() << std::endl
diff --git a/src/armnnConverter/ArmnnConverter.cpp b/src/armnnConverter/ArmnnConverter.cpp
index c1190d8..0129204 100644
--- a/src/armnnConverter/ArmnnConverter.cpp
+++ b/src/armnnConverter/ArmnnConverter.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2017 Arm Ltd. All rights reserved.
+// Copyright © 2017, 2023 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 #include <armnn/Logging.hpp>
@@ -151,7 +151,7 @@
             return EXIT_FAILURE;
         }
     }
-    catch (const cxxopts::OptionException& e)
+    catch (const cxxopts::exceptions::exception& e)
     {
         std::cerr << e.what() << std::endl << std::endl;
         return EXIT_FAILURE;
diff --git a/tests/ExecuteNetwork/ExecuteNetworkProgramOptions.cpp b/tests/ExecuteNetwork/ExecuteNetworkProgramOptions.cpp
index 007f818..3a54b1a 100644
--- a/tests/ExecuteNetwork/ExecuteNetworkProgramOptions.cpp
+++ b/tests/ExecuteNetwork/ExecuteNetworkProgramOptions.cpp
@@ -34,7 +34,7 @@
     // Check that the given options are valid.
     if (option == nullptr || required == nullptr)
     {
-        throw cxxopts::OptionParseException("Invalid option to check dependency for");
+        throw cxxopts::exceptions::exception("Invalid option to check dependency for");
     }
 
     // Check that if 'option' is provided, 'required' is also provided.
@@ -42,7 +42,7 @@
     {
         if (CheckOption(result, required) == 0 || result[required].has_default())
         {
-            throw cxxopts::OptionParseException(
+            throw cxxopts::exceptions::exception(
                     std::string("Option '") + option + "' requires option '" + required + "'.");
         }
     }
@@ -514,7 +514,7 @@
          m_ExNetParams.m_OutputDetailsOnlyToStdOut) &&
         !m_ExNetParams.m_EnableProfiling)
     {
-        throw cxxopts::OptionParseException("You must enable profiling if you would like to output layer details");
+        throw cxxopts::exceptions::exception("You must enable profiling if you would like to output layer details");
     }
 
     // Some options can't be assigned directly because they need some post-processing:
@@ -617,4 +617,3 @@
         m_ExNetParams.m_ComparisonComputeDevices = GetBackendIDs(comparisonComputDevices);
     }
 }
-
diff --git a/tests/ImageCSVFileGenerator/ImageCSVFileGenerator.cpp b/tests/ImageCSVFileGenerator/ImageCSVFileGenerator.cpp
index a949da3..46aad43 100644
--- a/tests/ImageCSVFileGenerator/ImageCSVFileGenerator.cpp
+++ b/tests/ImageCSVFileGenerator/ImageCSVFileGenerator.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2017 Arm Ltd. All rights reserved.
+// Copyright © 2017, 2023 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
@@ -167,7 +167,7 @@
                 return false;
             }
         }
-        catch (const cxxopts::OptionException& e)
+        catch (const cxxopts::exceptions::exception& e)
         {
             std::cerr << e.what() << std::endl << std::endl;
             return false;
@@ -249,4 +249,4 @@
     }
 
     return 0;
-}
\ No newline at end of file
+}
diff --git a/tests/ImageTensorGenerator/ImageTensorGenerator.cpp b/tests/ImageTensorGenerator/ImageTensorGenerator.cpp
index d4a2103..6e5b8ca 100644
--- a/tests/ImageTensorGenerator/ImageTensorGenerator.cpp
+++ b/tests/ImageTensorGenerator/ImageTensorGenerator.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2017 Arm Ltd. All rights reserved.
+// Copyright © 2017, 2023 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
@@ -205,7 +205,7 @@
                 return false;
             }
         }
-        catch (const cxxopts::OptionException& e)
+        catch (const cxxopts::exceptions::exception& e)
         {
             std::cerr << e.what() << std::endl << std::endl;
             return false;
diff --git a/tests/InferenceTest.cpp b/tests/InferenceTest.cpp
index 01b8983..9949e06 100644
--- a/tests/InferenceTest.cpp
+++ b/tests/InferenceTest.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2017 Arm Ltd. All rights reserved.
+// Copyright © 2017, 2023 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 #include "InferenceTest.hpp"
@@ -61,7 +61,7 @@
         CheckRequiredOptions(result, required);
 
     }
-    catch (const cxxopts::OptionException& e)
+    catch (const cxxopts::exceptions::exception& e)
     {
         std::cerr << e.what() << std::endl << options.help() << std::endl;
         return false;
diff --git a/tests/InferenceTest.hpp b/tests/InferenceTest.hpp
index fb9c048..f8a7d5e 100644
--- a/tests/InferenceTest.hpp
+++ b/tests/InferenceTest.hpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2017 Arm Ltd. All rights reserved.
+// Copyright © 2017, 2023 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 #pragma once
@@ -28,7 +28,7 @@
     if (compute == armnn::Compute::Undefined)
     {
         in.setstate(std::ios_base::failbit);
-        throw cxxopts::OptionException(fmt::format("Unrecognised compute device: {}", token));
+        throw cxxopts::exceptions::exception(fmt::format("Unrecognised compute device: {}", token));
     }
     return in;
 }
@@ -41,7 +41,7 @@
     if (compute == armnn::Compute::Undefined)
     {
         in.setstate(std::ios_base::failbit);
-        throw cxxopts::OptionException(fmt::format("Unrecognised compute device: {}", token));
+        throw cxxopts::exceptions::exception(fmt::format("Unrecognised compute device: {}", token));
     }
     backend = compute;
     return in;
diff --git a/tests/ModelAccuracyTool-Armnn/ModelAccuracyTool-Armnn.cpp b/tests/ModelAccuracyTool-Armnn/ModelAccuracyTool-Armnn.cpp
index 9e5507b..b2fd5cb 100644
--- a/tests/ModelAccuracyTool-Armnn/ModelAccuracyTool-Armnn.cpp
+++ b/tests/ModelAccuracyTool-Armnn/ModelAccuracyTool-Armnn.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2017 Arm Ltd. All rights reserved.
+// Copyright © 2017, 2023 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
@@ -131,7 +131,7 @@
                 }
             }
         }
-        catch (const cxxopts::OptionException& e)
+        catch (const cxxopts::exceptions::exception& e)
         {
             std::cerr << e.what() << std::endl << std::endl;
             return EXIT_FAILURE;
@@ -498,4 +498,4 @@
         modelOutputLabels.push_back(predictionCategoryNames);
     }
     return modelOutputLabels;
-}
\ No newline at end of file
+}
diff --git a/tests/MultipleNetworksCifar10/MultipleNetworksCifar10.cpp b/tests/MultipleNetworksCifar10/MultipleNetworksCifar10.cpp
index 133b8d8..823cbbc 100644
--- a/tests/MultipleNetworksCifar10/MultipleNetworksCifar10.cpp
+++ b/tests/MultipleNetworksCifar10/MultipleNetworksCifar10.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2017 Arm Ltd. All rights reserved.
+// Copyright © 2017, 2023 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
@@ -83,7 +83,7 @@
                 }
             }
         }
-        catch (const cxxopts::OptionException& e)
+        catch (const cxxopts::exceptions::exception& e)
         {
             std::cerr << e.what() << std::endl << in_options.help() << std::endl;
             return EXIT_FAILURE;
diff --git a/tests/TfLiteMobilenetQuantized-Armnn/TfLiteMobilenetQuantized-Armnn.cpp b/tests/TfLiteMobilenetQuantized-Armnn/TfLiteMobilenetQuantized-Armnn.cpp
index 56b145d..e554111 100644
--- a/tests/TfLiteMobilenetQuantized-Armnn/TfLiteMobilenetQuantized-Armnn.cpp
+++ b/tests/TfLiteMobilenetQuantized-Armnn/TfLiteMobilenetQuantized-Armnn.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2017 Arm Ltd. All rights reserved.
+// Copyright © 2017, 2023 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 #include "../InferenceTest.hpp"
@@ -76,7 +76,7 @@
 
         auto result = options.parse(argc, argv);
     }
-    catch (const cxxopts::OptionException& e)
+    catch (const cxxopts::exceptions::exception& e)
     {
         std::cerr << e.what() << std::endl;
         exit(EXIT_FAILURE);
diff --git a/tests/TfLiteYoloV3Big-Armnn/TfLiteYoloV3Big-Armnn.cpp b/tests/TfLiteYoloV3Big-Armnn/TfLiteYoloV3Big-Armnn.cpp
index 3ecd160..355fa25 100644
--- a/tests/TfLiteYoloV3Big-Armnn/TfLiteYoloV3Big-Armnn.cpp
+++ b/tests/TfLiteYoloV3Big-Armnn/TfLiteYoloV3Big-Armnn.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2020 Arm Ltd. All rights reserved.
+// Copyright © 2020, 2023 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
@@ -417,7 +417,7 @@
             {
                 std::stringstream ss;
                 ss << "Argument given to" << argName << "is not a valid file path";
-                throw cxxopts::option_syntax_exception(ss.str().c_str());
+                throw cxxopts::exceptions::invalid_option_syntax(ss.str().c_str());
             }
             return path;
         }
@@ -428,7 +428,7 @@
                 return "";
             }
 
-            throw cxxopts::missing_argument_exception(argName);
+            throw cxxopts::exceptions::missing_argument(argName);
         }
     }
 
@@ -440,7 +440,7 @@
             {
                 return std::vector<std::string>();
             }
-            throw cxxopts::option_syntax_exception("Comparison files requires 5 file paths.");
+            throw cxxopts::exceptions::invalid_option_syntax("Comparison files requires 5 file paths.");
         }
 
         std::vector<std::string> filePaths;
@@ -449,7 +449,8 @@
             filePaths.push_back(path);
             if (!ValidateFilePath(filePaths.back(), ExpectFile::True))
             {
-                throw cxxopts::option_syntax_exception("Argument given to Comparison Files is not a valid file path");
+                throw cxxopts::exceptions::invalid_option_syntax(
+                    "Argument given to Comparison Files is not a valid file path");
             }
         }
         return filePaths;
diff --git a/tests/profiling/gatordmock/CommandLineProcessor.cpp b/tests/profiling/gatordmock/CommandLineProcessor.cpp
index c1b6a6b..a532401 100644
--- a/tests/profiling/gatordmock/CommandLineProcessor.cpp
+++ b/tests/profiling/gatordmock/CommandLineProcessor.cpp
@@ -1,5 +1,5 @@
 //
-// Copyright © 2019 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2019, 2023 Arm Ltd and Contributors. All rights reserved.
 // SPDX-License-Identifier: MIT
 //
 
@@ -63,7 +63,7 @@
             m_Echo = true;
         }
     }
-    catch (const cxxopts::OptionException& e)
+    catch (const cxxopts::exceptions::exception& e)
     {
         std::cerr << e.what() << std::endl;
         return false;
@@ -74,4 +74,4 @@
 
 } // namespace gatordmock
 
-} // namespace armnn
\ No newline at end of file
+} // namespace armnn
diff --git a/third-party/cxxopts/CHANGELOG.md b/third-party/cxxopts/CHANGELOG.md
index df0e110..782f4d0 100644
--- a/third-party/cxxopts/CHANGELOG.md
+++ b/third-party/cxxopts/CHANGELOG.md
@@ -3,6 +3,31 @@
 This is the changelog for `cxxopts`, a C++11 library for parsing command line
 options. The project adheres to semantic versioning.
 
+## 3.1.1
+
+### Bug Fixes
+
+* Fixed version number in header.
+* Fixed cast warning in Unicode function.
+
+## 3.1
+
+### Added
+
+* Support for multiple long names for the same option (= multiple long aliases)
+* Add a `program()` function to retrieve the program name.
+* Added a .clang-format file.
+* Added iterator and printing for a ParseResult.
+
+### Changed
+
+* Cleanup exception code, add cxxopts::exceptions namespace.
+* Renamed several exceptions to be more descriptive, and added to a nested namespace.
+
+### Bug Fixes
+
+* Fix `arguments()` having no key for options that only have a short name.
+
 ## 3.0
 
 ### Changed
@@ -15,10 +40,19 @@
 * Change argument type in `Options::parse` from `char**` to `const char**`.
 * Refactor parser to not change its arguments.
 * `ParseResult` doesn't depend on a reference to the parser.
+* Fixed several warnings and code quality issues.
+* Improved formatting for help descriptions.
+* Improve integer parsing.
 
 ### Added
 
 * A list of unmatched arguments is available in `ParseResult`.
+* Support single letter options with argument attached.
+* Use <optional> if it is present.
+
+### Bug Fixes
+
+* Fix missing option name in exception.
 
 ## 2.2
 
diff --git a/third-party/cxxopts/CHANGELOG.md.license b/third-party/cxxopts/CHANGELOG.md.license
index bf1b5b5..2a9007a 100644
--- a/third-party/cxxopts/CHANGELOG.md.license
+++ b/third-party/cxxopts/CHANGELOG.md.license
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2014 Jarryd Beck
+# Copyright (c) 2014-2022 Jarryd Beck
 # SPDX-License-Identifier: MIT
 #
diff --git a/third-party/cxxopts/LICENSE.spdx b/third-party/cxxopts/LICENSE.spdx
index e4139cb..079b1a7 100644
--- a/third-party/cxxopts/LICENSE.spdx
+++ b/third-party/cxxopts/LICENSE.spdx
@@ -4,4 +4,4 @@
 PackageLicenseConcluded: MIT
 PackageLicenseInfoFromFiles: MIT
 PackageLicenseDeclared: MIT
-PackageCopyrightText: <text>Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck</text>
+PackageCopyrightText: <text>Copyright (c) 2014-2022 Jarryd Beck</text>
diff --git a/third-party/cxxopts/README.md b/third-party/cxxopts/README.md
index 9517993..de69e5a 100644
--- a/third-party/cxxopts/README.md
+++ b/third-party/cxxopts/README.md
@@ -7,9 +7,9 @@
 
 ## Version 3 breaking changes
 
-If you have used version 2, there are a couple of breaking changes in (the as
-yet unreleased, current master) version 3 that you should be aware of. If you are new to
-`cxxopts` you can skip this section.
+If you have used version 2, there are a couple of breaking changes in version 3
+that you should be aware of. If you are new to `cxxopts` you can skip this
+section.
 
 The parser no longer modifies its arguments, so you can pass a const `argc` and
 `argv` and expect them not to be changed.
@@ -109,9 +109,9 @@
 
 Exceptional situations throw C++ exceptions. There are two types of
 exceptions: errors defining the options, and errors when parsing a list of
-arguments. All exceptions derive from `cxxopts::OptionException`. Errors
-defining options derive from `cxxopts::OptionSpecException` and errors
-parsing arguments derive from `cxxopts::OptionParseException`.
+arguments. All exceptions derive from `cxxopts::exceptions::exception`. Errors
+defining options derive from `cxxopts::exceptions::specification` and errors
+parsing arguments derive from `cxxopts::exceptions::parsing`.
 
 All exceptions define a `what()` function to get a printable string
 explaining the error.
@@ -125,15 +125,37 @@
 
 ## Positional Arguments
 
-Positional arguments can be optionally parsed into one or more options.
-To set up positional arguments, call
+Positional arguments are those given without a preceding flag and can be used
+alongside non-positional arguments. There may be multiple positional arguments,
+and the final positional argument may be a container type to hold a list of all
+remaining positionals.
+
+To set up positional arguments, first declare the options, then configure a
+set of those arguments as positional like:
 
 ```cpp
-options.parse_positional({"first", "second", "last"})
+options.add_options()
+  ("script", "The script file to execute", cxxopts::value<std::string>())
+  ("server", "The server to execute on", cxxopts::value<std::string>())
+  ("filenames", "The filename(s) to process", cxxopts::value<std::vector<std::string>>());
+
+options.parse_positional({"script", "server", "filenames"});
+
+// Parse options the usual way
+options.parse(argc, argv);
 ```
 
-where "last" should be the name of an option with a container type, and the
-others should have a single value.
+For example, parsing the following arguments:
+~~~
+my_script.py my_server.com file1.txt file2.txt file3.txt
+~~~
+will result in parsed arguments like the following table:
+
+| Field         | Value                                     |
+| ------------- | ----------------------------------------- |
+| `"script"`    | `"my_script.py"`                          |
+| `"server"`    | `"my_server.com"`                         |
+| `"filenames"` | `{"file1.txt", "file2.txt", "file3.txt"}` |
 
 ## Default and implicit values
 
@@ -161,6 +183,8 @@
 regardless of the type that you want to store it in. It will be parsed as
 though it was given on the command line.
 
+Default values are not counted by `Options::count`.
+
 ## Boolean values
 
 Boolean options have a default implicit value of `"true"`, which can be
diff --git a/third-party/cxxopts/README.md.license b/third-party/cxxopts/README.md.license
index bf1b5b5..2a9007a 100644
--- a/third-party/cxxopts/README.md.license
+++ b/third-party/cxxopts/README.md.license
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2014 Jarryd Beck
+# Copyright (c) 2014-2022 Jarryd Beck
 # SPDX-License-Identifier: MIT
 #
diff --git a/third-party/cxxopts/cxxopts.hpp b/third-party/cxxopts/cxxopts.hpp
index 5cebc4b..8aa17bd 100644
--- a/third-party/cxxopts/cxxopts.hpp
+++ b/third-party/cxxopts/cxxopts.hpp
@@ -1,7 +1,6 @@
 /*
 
-Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck
-
+Copyright (c) 2014-2022 Jarryd Beck
 SPDX-License-Identifier: MIT
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -24,28 +23,55 @@
 
 */
 
+// vim: ts=2:sw=2:expandtab
+
 #ifndef CXXOPTS_HPP_INCLUDED
 #define CXXOPTS_HPP_INCLUDED
 
-#include <cctype>
 #include <cstring>
 #include <exception>
-#include <iostream>
 #include <limits>
-#include <list>
+#include <initializer_list>
 #include <map>
 #include <memory>
-#include <regex>
 #include <sstream>
 #include <string>
 #include <unordered_map>
 #include <unordered_set>
 #include <utility>
 #include <vector>
+#include <algorithm>
+#include <locale>
 
-#ifdef __cpp_lib_optional
-#include <optional>
-#define CXXOPTS_HAS_OPTIONAL
+#ifdef CXXOPTS_NO_EXCEPTIONS
+#include <iostream>
+#endif
+
+#if defined(__GNUC__) && !defined(__clang__)
+#  if (__GNUC__ * 10 + __GNUC_MINOR__) < 49
+#    define CXXOPTS_NO_REGEX true
+#  endif
+#endif
+#if defined(_MSC_VER) && !defined(__clang__)
+#define CXXOPTS_LINKONCE_CONST	__declspec(selectany) extern
+#define CXXOPTS_LINKONCE		__declspec(selectany) extern
+#else
+#define CXXOPTS_LINKONCE_CONST
+#define CXXOPTS_LINKONCE
+#endif
+
+#ifndef CXXOPTS_NO_REGEX
+#  include <regex>
+#endif  // CXXOPTS_NO_REGEX
+
+// Nonstandard before C++17, which is coincidentally what we also need for <optional>
+#ifdef __has_include
+#  if __has_include(<optional>)
+#    include <optional>
+#    ifdef __cpp_lib_optional
+#      define CXXOPTS_HAS_OPTIONAL
+#    endif
+#  endif
 #endif
 
 #if __cplusplus >= 201603L
@@ -59,18 +85,39 @@
 #endif
 
 #define CXXOPTS__VERSION_MAJOR 3
-#define CXXOPTS__VERSION_MINOR 0
-#define CXXOPTS__VERSION_PATCH 0
+#define CXXOPTS__VERSION_MINOR 1
+#define CXXOPTS__VERSION_PATCH 1
 
-namespace cxxopts
-{
-  static constexpr struct {
-    uint8_t major, minor, patch;
-  } version = {
-    CXXOPTS__VERSION_MAJOR,
-    CXXOPTS__VERSION_MINOR,
-    CXXOPTS__VERSION_PATCH
-  };
+#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6
+  #define CXXOPTS_NULL_DEREF_IGNORE
+#endif
+
+#if defined(__GNUC__)
+#define DO_PRAGMA(x) _Pragma(#x)
+#define CXXOPTS_DIAGNOSTIC_PUSH DO_PRAGMA(GCC diagnostic push)
+#define CXXOPTS_DIAGNOSTIC_POP DO_PRAGMA(GCC diagnostic pop)
+#define CXXOPTS_IGNORE_WARNING(x) DO_PRAGMA(GCC diagnostic ignored x)
+#else
+// define other compilers here if needed
+#define CXXOPTS_DIAGNOSTIC_PUSH
+#define CXXOPTS_DIAGNOSTIC_POP
+#define CXXOPTS_IGNORE_WARNING(x)
+#endif
+
+#ifdef CXXOPTS_NO_RTTI
+#define CXXOPTS_RTTI_CAST static_cast
+#else
+#define CXXOPTS_RTTI_CAST dynamic_cast
+#endif
+
+namespace cxxopts {
+static constexpr struct {
+  uint8_t major, minor, patch;
+} version = {
+  CXXOPTS__VERSION_MAJOR,
+  CXXOPTS__VERSION_MINOR,
+  CXXOPTS__VERSION_PATCH
+};
 } // namespace cxxopts
 
 //when we ask cxxopts to use Unicode, help strings are processed using ICU,
@@ -82,1602 +129,2064 @@
 #ifdef CXXOPTS_USE_UNICODE
 #include <unicode/unistr.h>
 
-namespace cxxopts
+namespace cxxopts {
+
+using String = icu::UnicodeString;
+
+inline
+String
+toLocalString(std::string s)
 {
-  typedef icu::UnicodeString String;
+  return icu::UnicodeString::fromUTF8(std::move(s));
+}
 
-  inline
-  String
-  toLocalString(std::string s)
+// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it:
+// warning: base class 'class std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual destructor
+CXXOPTS_DIAGNOSTIC_PUSH
+CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor")
+// This will be ignored under other compilers like LLVM clang.
+class UnicodeStringIterator
+{
+  public:
+
+  using iterator_category = std::forward_iterator_tag;
+  using value_type = int32_t;
+  using difference_type = std::ptrdiff_t;
+  using pointer = value_type*;
+  using reference = value_type&;
+
+  UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos)
+  : s(string)
+  , i(pos)
   {
-    return icu::UnicodeString::fromUTF8(std::move(s));
   }
 
-  class UnicodeStringIterator : public
-    std::iterator<std::forward_iterator_tag, int32_t>
+  value_type
+  operator*() const
   {
-    public:
-
-    UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos)
-    : s(string)
-    , i(pos)
-    {
-    }
-
-    value_type
-    operator*() const
-    {
-      return s->char32At(i);
-    }
-
-    bool
-    operator==(const UnicodeStringIterator& rhs) const
-    {
-      return s == rhs.s && i == rhs.i;
-    }
-
-    bool
-    operator!=(const UnicodeStringIterator& rhs) const
-    {
-      return !(*this == rhs);
-    }
-
-    UnicodeStringIterator&
-    operator++()
-    {
-      ++i;
-      return *this;
-    }
-
-    UnicodeStringIterator
-    operator+(int32_t v)
-    {
-      return UnicodeStringIterator(s, i + v);
-    }
-
-    private:
-    const icu::UnicodeString* s;
-    int32_t i;
-  };
-
-  inline
-  String&
-  stringAppend(String&s, String a)
-  {
-    return s.append(std::move(a));
+    return s->char32At(i);
   }
 
-  inline
-  String&
-  stringAppend(String& s, size_t n, UChar32 c)
-  {
-    for (size_t i = 0; i != n; ++i)
-    {
-      s.append(c);
-    }
-
-    return s;
-  }
-
-  template <typename Iterator>
-  String&
-  stringAppend(String& s, Iterator begin, Iterator end)
-  {
-    while (begin != end)
-    {
-      s.append(*begin);
-      ++begin;
-    }
-
-    return s;
-  }
-
-  inline
-  size_t
-  stringLength(const String& s)
-  {
-    return s.length();
-  }
-
-  inline
-  std::string
-  toUTF8String(const String& s)
-  {
-    std::string result;
-    s.toUTF8String(result);
-
-    return result;
-  }
-
-  inline
   bool
-  empty(const String& s)
+  operator==(const UnicodeStringIterator& rhs) const
   {
-    return s.isEmpty();
+    return s == rhs.s && i == rhs.i;
   }
-}
 
-namespace std
+  bool
+  operator!=(const UnicodeStringIterator& rhs) const
+  {
+    return !(*this == rhs);
+  }
+
+  UnicodeStringIterator&
+  operator++()
+  {
+    ++i;
+    return *this;
+  }
+
+  UnicodeStringIterator
+  operator+(int32_t v)
+  {
+    return UnicodeStringIterator(s, i + v);
+  }
+
+  private:
+  const icu::UnicodeString* s;
+  int32_t i;
+};
+CXXOPTS_DIAGNOSTIC_POP
+
+inline
+String&
+stringAppend(String&s, String a)
 {
-  inline
-  cxxopts::UnicodeStringIterator
-  begin(const icu::UnicodeString& s)
+  return s.append(std::move(a));
+}
+
+inline
+String&
+stringAppend(String& s, std::size_t n, UChar32 c)
+{
+  for (std::size_t i = 0; i != n; ++i)
   {
-    return cxxopts::UnicodeStringIterator(&s, 0);
+    s.append(c);
   }
 
-  inline
-  cxxopts::UnicodeStringIterator
-  end(const icu::UnicodeString& s)
-  {
-    return cxxopts::UnicodeStringIterator(&s, s.length());
-  }
+  return s;
 }
 
+template <typename Iterator>
+String&
+stringAppend(String& s, Iterator begin, Iterator end)
+{
+  while (begin != end)
+  {
+    s.append(*begin);
+    ++begin;
+  }
+
+  return s;
+}
+
+inline
+size_t
+stringLength(const String& s)
+{
+  return static_cast<size_t>(s.length());
+}
+
+inline
+std::string
+toUTF8String(const String& s)
+{
+  std::string result;
+  s.toUTF8String(result);
+
+  return result;
+}
+
+inline
+bool
+empty(const String& s)
+{
+  return s.isEmpty();
+}
+
+} // namespace cxxopts
+
+namespace std {
+
+inline
+cxxopts::UnicodeStringIterator
+begin(const icu::UnicodeString& s)
+{
+  return cxxopts::UnicodeStringIterator(&s, 0);
+}
+
+inline
+cxxopts::UnicodeStringIterator
+end(const icu::UnicodeString& s)
+{
+  return cxxopts::UnicodeStringIterator(&s, s.length());
+}
+
+} // namespace std
+
 //ifdef CXXOPTS_USE_UNICODE
 #else
 
-namespace cxxopts
+namespace cxxopts {
+
+using String = std::string;
+
+template <typename T>
+T
+toLocalString(T&& t)
 {
-  typedef std::string String;
+  return std::forward<T>(t);
+}
 
-  template <typename T>
-  T
-  toLocalString(T&& t)
-  {
-    return std::forward<T>(t);
-  }
+inline
+std::size_t
+stringLength(const String& s)
+{
+  return s.length();
+}
 
-  inline
-  size_t
-  stringLength(const String& s)
-  {
-    return s.length();
-  }
+inline
+String&
+stringAppend(String&s, const String& a)
+{
+  return s.append(a);
+}
 
-  inline
-  String&
-  stringAppend(String&s, const String& a)
-  {
-    return s.append(a);
-  }
+inline
+String&
+stringAppend(String& s, std::size_t n, char c)
+{
+  return s.append(n, c);
+}
 
-  inline
-  String&
-  stringAppend(String& s, size_t n, char c)
-  {
-    return s.append(n, c);
-  }
+template <typename Iterator>
+String&
+stringAppend(String& s, Iterator begin, Iterator end)
+{
+  return s.append(begin, end);
+}
 
-  template <typename Iterator>
-  String&
-  stringAppend(String& s, Iterator begin, Iterator end)
-  {
-    return s.append(begin, end);
-  }
+template <typename T>
+std::string
+toUTF8String(T&& t)
+{
+  return std::forward<T>(t);
+}
 
-  template <typename T>
-  std::string
-  toUTF8String(T&& t)
-  {
-    return std::forward<T>(t);
-  }
+inline
+bool
+empty(const std::string& s)
+{
+  return s.empty();
+}
 
-  inline
-  bool
-  empty(const std::string& s)
-  {
-    return s.empty();
-  }
 } // namespace cxxopts
 
 //ifdef CXXOPTS_USE_UNICODE
 #endif
 
-namespace cxxopts
+namespace cxxopts {
+
+namespace {
+CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'");
+CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'");
+} // namespace
+
+// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we
+// want to silence it: warning: base class 'class
+// std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual
+// destructor This will be ignored under other compilers like LLVM clang.
+CXXOPTS_DIAGNOSTIC_PUSH
+CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor")
+
+// some older versions of GCC warn under this warning
+CXXOPTS_IGNORE_WARNING("-Weffc++")
+class Value : public std::enable_shared_from_this<Value>
 {
-  namespace
+  public:
+
+  virtual ~Value() = default;
+
+  virtual
+  std::shared_ptr<Value>
+  clone() const = 0;
+
+  virtual void
+  parse(const std::string& text) const = 0;
+
+  virtual void
+  parse() const = 0;
+
+  virtual bool
+  has_default() const = 0;
+
+  virtual bool
+  is_container() const = 0;
+
+  virtual bool
+  has_implicit() const = 0;
+
+  virtual std::string
+  get_default_value() const = 0;
+
+  virtual std::string
+  get_implicit_value() const = 0;
+
+  virtual std::shared_ptr<Value>
+  default_value(const std::string& value) = 0;
+
+  virtual std::shared_ptr<Value>
+  implicit_value(const std::string& value) = 0;
+
+  virtual std::shared_ptr<Value>
+  no_implicit_value() = 0;
+
+  virtual bool
+  is_boolean() const = 0;
+};
+
+CXXOPTS_DIAGNOSTIC_POP
+
+namespace exceptions {
+
+class exception : public std::exception
+{
+  public:
+  explicit exception(std::string  message)
+  : m_message(std::move(message))
   {
-#ifdef _WIN32
-    const std::string LQUOTE("\'");
-    const std::string RQUOTE("\'");
-#else
-    const std::string LQUOTE("‘");
-    const std::string RQUOTE("’");
-#endif
-  } // namespace
+  }
 
-#if defined(__GNUC__)
-// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it:
-// warning: base class 'class std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual destructor
-#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
-#pragma GCC diagnostic push
-// This will be ignored under other compilers like LLVM clang.
-#endif
-  class Value : public std::enable_shared_from_this<Value>
+  CXXOPTS_NODISCARD
+  const char*
+  what() const noexcept override
   {
-    public:
+    return m_message.c_str();
+  }
 
-    virtual ~Value() = default;
+  private:
+  std::string m_message;
+};
 
-    virtual
-    std::shared_ptr<Value>
-    clone() const = 0;
+class specification : public exception
+{
+  public:
 
-    virtual void
-    parse(const std::string& text) const = 0;
-
-    virtual void
-    parse() const = 0;
-
-    virtual bool
-    has_default() const = 0;
-
-    virtual bool
-    is_container() const = 0;
-
-    virtual bool
-    has_implicit() const = 0;
-
-    virtual std::string
-    get_default_value() const = 0;
-
-    virtual std::string
-    get_implicit_value() const = 0;
-
-    virtual std::shared_ptr<Value>
-    default_value(const std::string& value) = 0;
-
-    virtual std::shared_ptr<Value>
-    implicit_value(const std::string& value) = 0;
-
-    virtual std::shared_ptr<Value>
-    no_implicit_value() = 0;
-
-    virtual bool
-    is_boolean() const = 0;
-  };
-#if defined(__GNUC__)
-#pragma GCC diagnostic pop
-#endif
-  class OptionException : public std::exception
+  explicit specification(const std::string& message)
+  : exception(message)
   {
-    public:
-    explicit OptionException(std::string  message)
-    : m_message(std::move(message))
-    {
-    }
+  }
+};
 
-    CXXOPTS_NODISCARD
-    const char*
-    what() const noexcept override
-    {
-      return m_message.c_str();
-    }
-
-    private:
-    std::string m_message;
-  };
-
-  class OptionSpecException : public OptionException
+class parsing : public exception
+{
+  public:
+  explicit parsing(const std::string& message)
+  : exception(message)
   {
-    public:
+  }
+};
 
-    explicit OptionSpecException(const std::string& message)
-    : OptionException(message)
-    {
-    }
-  };
-
-  class OptionParseException : public OptionException
+class option_already_exists : public specification
+{
+  public:
+  explicit option_already_exists(const std::string& option)
+  : specification("Option " + LQUOTE + option + RQUOTE + " already exists")
   {
-    public:
-    explicit OptionParseException(const std::string& message)
-    : OptionException(message)
-    {
-    }
-  };
+  }
+};
 
-  class option_exists_error : public OptionSpecException
+class invalid_option_format : public specification
+{
+  public:
+  explicit invalid_option_format(const std::string& format)
+  : specification("Invalid option format " + LQUOTE + format + RQUOTE)
   {
-    public:
-    explicit option_exists_error(const std::string& option)
-    : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists")
-    {
-    }
-  };
+  }
+};
 
-  class invalid_option_format_error : public OptionSpecException
+class invalid_option_syntax : public parsing {
+  public:
+  explicit invalid_option_syntax(const std::string& text)
+  : parsing("Argument " + LQUOTE + text + RQUOTE +
+            " starts with a - but has incorrect syntax")
   {
-    public:
-    explicit invalid_option_format_error(const std::string& format)
-    : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE)
-    {
-    }
-  };
+  }
+};
 
-  class option_syntax_exception : public OptionParseException {
-    public:
-    explicit option_syntax_exception(const std::string& text)
-    : OptionParseException("Argument " + LQUOTE + text + RQUOTE +
-        " starts with a - but has incorrect syntax")
-    {
-    }
-  };
-
-  class option_not_exists_exception : public OptionParseException
+class no_such_option : public parsing
+{
+  public:
+  explicit no_such_option(const std::string& option)
+  : parsing("Option " + LQUOTE + option + RQUOTE + " does not exist")
   {
-    public:
-    explicit option_not_exists_exception(const std::string& option)
-    : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist")
-    {
-    }
-  };
+  }
+};
 
-  class missing_argument_exception : public OptionParseException
-  {
-    public:
-    explicit missing_argument_exception(const std::string& option)
-    : OptionParseException(
-        "Option " + LQUOTE + option + RQUOTE + " is missing an argument"
-      )
-    {
-    }
-  };
-
-  class option_requires_argument_exception : public OptionParseException
-  {
-    public:
-    explicit option_requires_argument_exception(const std::string& option)
-    : OptionParseException(
-        "Option " + LQUOTE + option + RQUOTE + " requires an argument"
-      )
-    {
-    }
-  };
-
-  class option_not_has_argument_exception : public OptionParseException
-  {
-    public:
-    option_not_has_argument_exception
-    (
-      const std::string& option,
-      const std::string& arg
+class missing_argument : public parsing
+{
+  public:
+  explicit missing_argument(const std::string& option)
+  : parsing(
+      "Option " + LQUOTE + option + RQUOTE + " is missing an argument"
     )
-    : OptionParseException(
-        "Option " + LQUOTE + option + RQUOTE +
-        " does not take an argument, but argument " +
-        LQUOTE + arg + RQUOTE + " given"
-      )
-    {
-    }
-  };
-
-  class option_not_present_exception : public OptionParseException
   {
-    public:
-    explicit option_not_present_exception(const std::string& option)
-    : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present")
-    {
-    }
-  };
+  }
+};
 
-  class option_has_no_value_exception : public OptionException
-  {
-    public:
-    explicit option_has_no_value_exception(const std::string& option)
-    : OptionException(
-        option.empty() ?
-        ("Option " + LQUOTE + option + RQUOTE + " has no value") :
-        "Option has no value")
-    {
-    }
-  };
-
-  class argument_incorrect_type : public OptionParseException
-  {
-    public:
-    explicit argument_incorrect_type
-    (
-      const std::string& arg
+class option_requires_argument : public parsing
+{
+  public:
+  explicit option_requires_argument(const std::string& option)
+  : parsing(
+      "Option " + LQUOTE + option + RQUOTE + " requires an argument"
     )
-    : OptionParseException(
-        "Argument " + LQUOTE + arg + RQUOTE + " failed to parse"
-      )
-    {
-    }
-  };
-
-  class option_required_exception : public OptionParseException
   {
-    public:
-    explicit option_required_exception(const std::string& option)
-    : OptionParseException(
-        "Option " + LQUOTE + option + RQUOTE + " is required but not present"
-      )
-    {
-    }
-  };
+  }
+};
 
-  template <typename T>
-  void throw_or_mimic(const std::string& text)
+class gratuitous_argument_for_option : public parsing
+{
+  public:
+  gratuitous_argument_for_option
+  (
+    const std::string& option,
+    const std::string& arg
+  )
+  : parsing(
+      "Option " + LQUOTE + option + RQUOTE +
+      " does not take an argument, but argument " +
+      LQUOTE + arg + RQUOTE + " given"
+    )
   {
-    static_assert(std::is_base_of<std::exception, T>::value,
-                  "throw_or_mimic only works on std::exception and "
-                  "deriving classes");
+  }
+};
+
+class requested_option_not_present : public parsing
+{
+  public:
+  explicit requested_option_not_present(const std::string& option)
+  : parsing("Option " + LQUOTE + option + RQUOTE + " not present")
+  {
+  }
+};
+
+class option_has_no_value : public exception
+{
+  public:
+  explicit option_has_no_value(const std::string& option)
+  : exception(
+      !option.empty() ?
+      ("Option " + LQUOTE + option + RQUOTE + " has no value") :
+      "Option has no value")
+  {
+  }
+};
+
+class incorrect_argument_type : public parsing
+{
+  public:
+  explicit incorrect_argument_type
+  (
+    const std::string& arg
+  )
+  : parsing(
+      "Argument " + LQUOTE + arg + RQUOTE + " failed to parse"
+    )
+  {
+  }
+};
+
+} // namespace exceptions
+
+
+template <typename T>
+void throw_or_mimic(const std::string& text)
+{
+  static_assert(std::is_base_of<std::exception, T>::value,
+                "throw_or_mimic only works on std::exception and "
+                "deriving classes");
 
 #ifndef CXXOPTS_NO_EXCEPTIONS
-    // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw
-    throw T{text};
+  // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw
+  throw T{text};
 #else
-    // Otherwise manually instantiate the exception, print what() to stderr,
-    // and exit
-    T exception{text};
-    std::cerr << exception.what() << std::endl;
-    std::exit(EXIT_FAILURE);
+  // Otherwise manually instantiate the exception, print what() to stderr,
+  // and exit
+  T exception{text};
+  std::cerr << exception.what() << std::endl;
+  std::exit(EXIT_FAILURE);
 #endif
+}
+
+using OptionNames = std::vector<std::string>;
+
+namespace values {
+
+namespace parser_tool {
+
+struct IntegerDesc
+{
+  std::string negative = "";
+  std::string base     = "";
+  std::string value    = "";
+};
+struct ArguDesc {
+  std::string arg_name  = "";
+  bool        grouping  = false;
+  bool        set_value = false;
+  std::string value     = "";
+};
+
+#ifdef CXXOPTS_NO_REGEX
+inline IntegerDesc SplitInteger(const std::string &text)
+{
+  if (text.empty())
+  {
+    throw_or_mimic<exceptions::incorrect_argument_type>(text);
+  }
+  IntegerDesc desc;
+  const char *pdata = text.c_str();
+  if (*pdata == '-')
+  {
+    pdata += 1;
+    desc.negative = "-";
+  }
+  if (strncmp(pdata, "0x", 2) == 0)
+  {
+    pdata += 2;
+    desc.base = "0x";
+  }
+  if (*pdata != '\0')
+  {
+    desc.value = std::string(pdata);
+  }
+  else
+  {
+    throw_or_mimic<exceptions::incorrect_argument_type>(text);
+  }
+  return desc;
+}
+
+inline bool IsTrueText(const std::string &text)
+{
+  const char *pdata = text.c_str();
+  if (*pdata == 't' || *pdata == 'T')
+  {
+    pdata += 1;
+    if (strncmp(pdata, "rue\0", 4) == 0)
+    {
+      return true;
+    }
+  }
+  else if (strncmp(pdata, "1\0", 2) == 0)
+  {
+    return true;
+  }
+  return false;
+}
+
+inline bool IsFalseText(const std::string &text)
+{
+  const char *pdata = text.c_str();
+  if (*pdata == 'f' || *pdata == 'F')
+  {
+    pdata += 1;
+    if (strncmp(pdata, "alse\0", 5) == 0)
+    {
+      return true;
+    }
+  }
+  else if (strncmp(pdata, "0\0", 2) == 0)
+  {
+    return true;
+  }
+  return false;
+}
+
+inline OptionNames split_option_names(const std::string &text)
+{
+  OptionNames split_names;
+
+  std::string::size_type token_start_pos = 0;
+  auto length = text.length();
+
+  if (length == 0)
+  {
+    throw_or_mimic<exceptions::invalid_option_format>(text);
   }
 
-  namespace values
+  while (token_start_pos < length) {
+    const auto &npos = std::string::npos;
+    auto next_non_space_pos = text.find_first_not_of(' ', token_start_pos);
+    if (next_non_space_pos == npos) {
+      throw_or_mimic<exceptions::invalid_option_format>(text);
+    }
+    token_start_pos = next_non_space_pos;
+    auto next_delimiter_pos = text.find(',', token_start_pos);
+    if (next_delimiter_pos == token_start_pos) {
+      throw_or_mimic<exceptions::invalid_option_format>(text);
+    }
+    if (next_delimiter_pos == npos) {
+      next_delimiter_pos = length;
+    }
+    auto token_length = next_delimiter_pos - token_start_pos;
+    // validate the token itself matches the regex /([:alnum:][-_[:alnum:]]*/
+    {
+      const char* option_name_valid_chars =
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+        "abcdefghijklmnopqrstuvwxyz"
+        "0123456789"
+        "_-.?";
+
+      if (!std::isalnum(text[token_start_pos], std::locale::classic()) ||
+          text.find_first_not_of(option_name_valid_chars, token_start_pos) < next_delimiter_pos) {
+        throw_or_mimic<exceptions::invalid_option_format>(text);
+      }
+    }
+    split_names.emplace_back(text.substr(token_start_pos, token_length));
+    token_start_pos = next_delimiter_pos + 1;
+  }
+  return split_names;
+}
+
+inline ArguDesc ParseArgument(const char *arg, bool &matched)
+{
+  ArguDesc argu_desc;
+  const char *pdata = arg;
+  matched = false;
+  if (strncmp(pdata, "--", 2) == 0)
   {
-    namespace
+    pdata += 2;
+    if (isalnum(*pdata, std::locale::classic()))
     {
-      std::basic_regex<char> integer_pattern
-        ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)");
-      std::basic_regex<char> truthy_pattern
-        ("(t|T)(rue)?|1");
-      std::basic_regex<char> falsy_pattern
-        ("(f|F)(alse)?|0");
-    } // namespace
-
-    namespace detail
-    {
-      template <typename T, bool B>
-      struct SignedCheck;
-
-      template <typename T>
-      struct SignedCheck<T, true>
+      argu_desc.arg_name.push_back(*pdata);
+      pdata += 1;
+      while (isalnum(*pdata, std::locale::classic()) || *pdata == '-' || *pdata == '_')
       {
-        template <typename U>
-        void
-        operator()(bool negative, U u, const std::string& text)
+        argu_desc.arg_name.push_back(*pdata);
+        pdata += 1;
+      }
+      if (argu_desc.arg_name.length() > 1)
+      {
+        if (*pdata == '=')
         {
-          if (negative)
+          argu_desc.set_value = true;
+          pdata += 1;
+          if (*pdata != '\0')
           {
-            if (u > static_cast<U>((std::numeric_limits<T>::min)()))
-            {
-              throw_or_mimic<argument_incorrect_type>(text);
-            }
+            argu_desc.value = std::string(pdata);
           }
-          else
-          {
-            if (u > static_cast<U>((std::numeric_limits<T>::max)()))
-            {
-              throw_or_mimic<argument_incorrect_type>(text);
-            }
-          }
+          matched = true;
         }
-      };
-
-      template <typename T>
-      struct SignedCheck<T, false>
-      {
-        template <typename U>
-        void
-        operator()(bool, U, const std::string&) {}
-      };
-
-      template <typename T, typename U>
-      void
-      check_signed_range(bool negative, U value, const std::string& text)
-      {
-        SignedCheck<T, std::numeric_limits<T>::is_signed>()(negative, value, text);
-      }
-    } // namespace detail
-
-    template <typename R, typename T>
-    void
-    checked_negate(R& r, T&& t, const std::string&, std::true_type)
-    {
-      // if we got to here, then `t` is a positive number that fits into
-      // `R`. So to avoid MSVC C4146, we first cast it to `R`.
-      // See https://github.com/jarro2783/cxxopts/issues/62 for more details.
-      r = static_cast<R>(-static_cast<R>(t-1)-1);
-    }
-
-    template <typename R, typename T>
-    void
-    checked_negate(R&, T&&, const std::string& text, std::false_type)
-    {
-      throw_or_mimic<argument_incorrect_type>(text);
-    }
-
-    template <typename T>
-    void
-    integer_parser(const std::string& text, T& value)
-    {
-      std::smatch match;
-      std::regex_match(text, match, integer_pattern);
-
-      if (match.length() == 0)
-      {
-        throw_or_mimic<argument_incorrect_type>(text);
-      }
-
-      if (match.length(4) > 0)
-      {
-        value = 0;
-        return;
-      }
-
-      using US = typename std::make_unsigned<T>::type;
-
-      constexpr bool is_signed = std::numeric_limits<T>::is_signed;
-      const bool negative = match.length(1) > 0;
-      const uint8_t base = match.length(2) > 0 ? 16 : 10;
-
-      auto value_match = match[3];
-
-      US result = 0;
-
-      for (auto iter = value_match.first; iter != value_match.second; ++iter)
-      {
-        US digit = 0;
-
-        if (*iter >= '0' && *iter <= '9')
+        else if (*pdata == '\0')
         {
-          digit = static_cast<US>(*iter - '0');
+          matched = true;
         }
-        else if (base == 16 && *iter >= 'a' && *iter <= 'f')
-        {
-          digit = static_cast<US>(*iter - 'a' + 10);
-        }
-        else if (base == 16 && *iter >= 'A' && *iter <= 'F')
-        {
-          digit = static_cast<US>(*iter - 'A' + 10);
-        }
-        else
-        {
-          throw_or_mimic<argument_incorrect_type>(text);
-        }
-
-        const US next = static_cast<US>(result * base + digit);
-        if (result > next)
-        {
-          throw_or_mimic<argument_incorrect_type>(text);
-        }
-
-        result = next;
       }
+    }
+  }
+  else if (strncmp(pdata, "-", 1) == 0)
+  {
+    pdata += 1;
+    argu_desc.grouping = true;
+    while (isalnum(*pdata, std::locale::classic()))
+    {
+      argu_desc.arg_name.push_back(*pdata);
+      pdata += 1;
+    }
+    matched = !argu_desc.arg_name.empty() && *pdata == '\0';
+  }
+  return argu_desc;
+}
 
-      detail::check_signed_range<T>(negative, result, text);
+#else  // CXXOPTS_NO_REGEX
 
-      if (negative)
+namespace {
+CXXOPTS_LINKONCE
+std::basic_regex<char> integer_pattern
+  ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)");
+CXXOPTS_LINKONCE
+std::basic_regex<char> truthy_pattern
+  ("(t|T)(rue)?|1");
+CXXOPTS_LINKONCE
+std::basic_regex<char> falsy_pattern
+  ("(f|F)(alse)?|0");
+CXXOPTS_LINKONCE
+std::basic_regex<char> option_matcher
+  ("--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)");
+CXXOPTS_LINKONCE
+std::basic_regex<char> option_specifier
+  ("([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*");
+CXXOPTS_LINKONCE
+std::basic_regex<char> option_specifier_separator(", *");
+
+} // namespace
+
+inline IntegerDesc SplitInteger(const std::string &text)
+{
+  std::smatch match;
+  std::regex_match(text, match, integer_pattern);
+
+  if (match.length() == 0)
+  {
+    throw_or_mimic<exceptions::incorrect_argument_type>(text);
+  }
+
+  IntegerDesc desc;
+  desc.negative = match[1];
+  desc.base = match[2];
+  desc.value = match[3];
+
+  if (match.length(4) > 0)
+  {
+    desc.base = match[5];
+    desc.value = "0";
+    return desc;
+  }
+
+  return desc;
+}
+
+inline bool IsTrueText(const std::string &text)
+{
+  std::smatch result;
+  std::regex_match(text, result, truthy_pattern);
+  return !result.empty();
+}
+
+inline bool IsFalseText(const std::string &text)
+{
+  std::smatch result;
+  std::regex_match(text, result, falsy_pattern);
+  return !result.empty();
+}
+
+// Gets the option names specified via a single, comma-separated string,
+// and returns the separate, space-discarded, non-empty names
+// (without considering which or how many are single-character)
+inline OptionNames split_option_names(const std::string &text)
+{
+  if (!std::regex_match(text.c_str(), option_specifier))
+  {
+    throw_or_mimic<exceptions::invalid_option_format>(text);
+  }
+
+  OptionNames split_names;
+
+  constexpr int use_non_matches { -1 };
+  auto token_iterator = std::sregex_token_iterator(
+    text.begin(), text.end(), option_specifier_separator, use_non_matches);
+  std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names));
+  return split_names;
+}
+
+inline ArguDesc ParseArgument(const char *arg, bool &matched)
+{
+  std::match_results<const char*> result;
+  std::regex_match(arg, result, option_matcher);
+  matched = !result.empty();
+
+  ArguDesc argu_desc;
+  if (matched) {
+    argu_desc.arg_name = result[1].str();
+    argu_desc.set_value = result[2].length() > 0;
+    argu_desc.value = result[3].str();
+    if (result[4].length() > 0)
+    {
+      argu_desc.grouping = true;
+      argu_desc.arg_name = result[4].str();
+    }
+  }
+
+  return argu_desc;
+}
+
+#endif  // CXXOPTS_NO_REGEX
+#undef CXXOPTS_NO_REGEX
+} // namespace parser_tool
+
+namespace detail {
+
+template <typename T, bool B>
+struct SignedCheck;
+
+template <typename T>
+struct SignedCheck<T, true>
+{
+  template <typename U>
+  void
+  operator()(bool negative, U u, const std::string& text)
+  {
+    if (negative)
+    {
+      if (u > static_cast<U>((std::numeric_limits<T>::min)()))
       {
-        checked_negate<T>(value, result, text, std::integral_constant<bool, is_signed>());
+        throw_or_mimic<exceptions::incorrect_argument_type>(text);
       }
-      else
+    }
+    else
+    {
+      if (u > static_cast<U>((std::numeric_limits<T>::max)()))
       {
-        value = static_cast<T>(result);
+        throw_or_mimic<exceptions::incorrect_argument_type>(text);
       }
     }
+  }
+};
 
-    template <typename T>
-    void stringstream_parser(const std::string& text, T& value)
+template <typename T>
+struct SignedCheck<T, false>
+{
+  template <typename U>
+  void
+  operator()(bool, U, const std::string&) const {}
+};
+
+template <typename T, typename U>
+void
+check_signed_range(bool negative, U value, const std::string& text)
+{
+  SignedCheck<T, std::numeric_limits<T>::is_signed>()(negative, value, text);
+}
+
+} // namespace detail
+
+template <typename R, typename T>
+void
+checked_negate(R& r, T&& t, const std::string&, std::true_type)
+{
+  // if we got to here, then `t` is a positive number that fits into
+  // `R`. So to avoid MSVC C4146, we first cast it to `R`.
+  // See https://github.com/jarro2783/cxxopts/issues/62 for more details.
+  r = static_cast<R>(-static_cast<R>(t-1)-1);
+}
+
+template <typename R, typename T>
+void
+checked_negate(R&, T&&, const std::string& text, std::false_type)
+{
+  throw_or_mimic<exceptions::incorrect_argument_type>(text);
+}
+
+template <typename T>
+void
+integer_parser(const std::string& text, T& value)
+{
+  parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text);
+
+  using US = typename std::make_unsigned<T>::type;
+  constexpr bool is_signed = std::numeric_limits<T>::is_signed;
+
+  const bool          negative    = int_desc.negative.length() > 0;
+  const uint8_t       base        = int_desc.base.length() > 0 ? 16 : 10;
+  const std::string & value_match = int_desc.value;
+
+  US result = 0;
+
+  for (char ch : value_match)
+  {
+    US digit = 0;
+
+    if (ch >= '0' && ch <= '9')
     {
-      std::stringstream in(text);
-      in >> value;
-      if (!in) {
-        throw_or_mimic<argument_incorrect_type>(text);
-      }
+      digit = static_cast<US>(ch - '0');
     }
-
-    inline
-    void
-    parse_value(const std::string& text, uint8_t& value)
+    else if (base == 16 && ch >= 'a' && ch <= 'f')
     {
-      integer_parser(text, value);
+      digit = static_cast<US>(ch - 'a' + 10);
     }
-
-    inline
-    void
-    parse_value(const std::string& text, int8_t& value)
+    else if (base == 16 && ch >= 'A' && ch <= 'F')
     {
-      integer_parser(text, value);
+      digit = static_cast<US>(ch - 'A' + 10);
     }
-
-    inline
-    void
-    parse_value(const std::string& text, uint16_t& value)
+    else
     {
-      integer_parser(text, value);
+      throw_or_mimic<exceptions::incorrect_argument_type>(text);
     }
 
-    inline
-    void
-    parse_value(const std::string& text, int16_t& value)
+    const US next = static_cast<US>(result * base + digit);
+    if (result > next)
     {
-      integer_parser(text, value);
+      throw_or_mimic<exceptions::incorrect_argument_type>(text);
     }
 
-    inline
-    void
-    parse_value(const std::string& text, uint32_t& value)
-    {
-      integer_parser(text, value);
-    }
+    result = next;
+  }
 
-    inline
-    void
-    parse_value(const std::string& text, int32_t& value)
-    {
-      integer_parser(text, value);
-    }
+  detail::check_signed_range<T>(negative, result, text);
 
-    inline
-    void
-    parse_value(const std::string& text, uint64_t& value)
-    {
-      integer_parser(text, value);
-    }
+  if (negative)
+  {
+    checked_negate<T>(value, result, text, std::integral_constant<bool, is_signed>());
+  }
+  else
+  {
+    value = static_cast<T>(result);
+  }
+}
 
-    inline
-    void
-    parse_value(const std::string& text, int64_t& value)
-    {
-      integer_parser(text, value);
-    }
+template <typename T>
+void stringstream_parser(const std::string& text, T& value)
+{
+  std::stringstream in(text);
+  in >> value;
+  if (!in) {
+    throw_or_mimic<exceptions::incorrect_argument_type>(text);
+  }
+}
 
-    inline
-    void
-    parse_value(const std::string& text, bool& value)
-    {
-      std::smatch result;
-      std::regex_match(text, result, truthy_pattern);
+template <typename T,
+         typename std::enable_if<std::is_integral<T>::value>::type* = nullptr
+         >
+void parse_value(const std::string& text, T& value)
+{
+    integer_parser(text, value);
+}
 
-      if (!result.empty())
-      {
-        value = true;
-        return;
-      }
+inline
+void
+parse_value(const std::string& text, bool& value)
+{
+  if (parser_tool::IsTrueText(text))
+  {
+    value = true;
+    return;
+  }
 
-      std::regex_match(text, result, falsy_pattern);
-      if (!result.empty())
-      {
-        value = false;
-        return;
-      }
+  if (parser_tool::IsFalseText(text))
+  {
+    value = false;
+    return;
+  }
 
-      throw_or_mimic<argument_incorrect_type>(text);
-    }
+  throw_or_mimic<exceptions::incorrect_argument_type>(text);
+}
 
-    inline
-    void
-    parse_value(const std::string& text, std::string& value)
-    {
-      value = text;
-    }
+inline
+void
+parse_value(const std::string& text, std::string& value)
+{
+  value = text;
+}
 
-    // The fallback parser. It uses the stringstream parser to parse all types
-    // that have not been overloaded explicitly.  It has to be placed in the
-    // source code before all other more specialized templates.
-    template <typename T>
-    void
-    parse_value(const std::string& text, T& value) {
-      stringstream_parser(text, value);
-    }
+// The fallback parser. It uses the stringstream parser to parse all types
+// that have not been overloaded explicitly.  It has to be placed in the
+// source code before all other more specialized templates.
+template <typename T,
+         typename std::enable_if<!std::is_integral<T>::value>::type* = nullptr
+         >
+void
+parse_value(const std::string& text, T& value) {
+  stringstream_parser(text, value);
+}
 
-    template <typename T>
-    void
-    parse_value(const std::string& text, std::vector<T>& value)
-    {
-      std::stringstream in(text);
-      std::string token;
-      while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
-        T v;
-        parse_value(token, v);
-        value.emplace_back(std::move(v));
-      }
-    }
+template <typename T>
+void
+parse_value(const std::string& text, std::vector<T>& value)
+{
+  if (text.empty()) {
+    T v;
+    parse_value(text, v);
+    value.emplace_back(std::move(v));
+    return;
+  }
+  std::stringstream in(text);
+  std::string token;
+  while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
+    T v;
+    parse_value(token, v);
+    value.emplace_back(std::move(v));
+  }
+}
 
 #ifdef CXXOPTS_HAS_OPTIONAL
-    template <typename T>
-    void
-    parse_value(const std::string& text, std::optional<T>& value)
-    {
-      T result;
-      parse_value(text, result);
-      value = std::move(result);
-    }
+template <typename T>
+void
+parse_value(const std::string& text, std::optional<T>& value)
+{
+  T result;
+  parse_value(text, result);
+  value = std::move(result);
+}
 #endif
 
-    inline
-    void parse_value(const std::string& text, char& c)
-    {
-      if (text.length() != 1)
-      {
-        throw_or_mimic<argument_incorrect_type>(text);
-      }
+inline
+void parse_value(const std::string& text, char& c)
+{
+  if (text.length() != 1)
+  {
+    throw_or_mimic<exceptions::incorrect_argument_type>(text);
+  }
 
-      c = text[0];
+  c = text[0];
+}
+
+template <typename T>
+struct type_is_container
+{
+  static constexpr bool value = false;
+};
+
+template <typename T>
+struct type_is_container<std::vector<T>>
+{
+  static constexpr bool value = true;
+};
+
+template <typename T>
+class abstract_value : public Value
+{
+  using Self = abstract_value<T>;
+
+  public:
+  abstract_value()
+  : m_result(std::make_shared<T>())
+  , m_store(m_result.get())
+  {
+  }
+
+  explicit abstract_value(T* t)
+  : m_store(t)
+  {
+  }
+
+  ~abstract_value() override = default;
+
+  abstract_value& operator=(const abstract_value&) = default;
+
+  abstract_value(const abstract_value& rhs)
+  {
+    if (rhs.m_result)
+    {
+      m_result = std::make_shared<T>();
+      m_store = m_result.get();
+    }
+    else
+    {
+      m_store = rhs.m_store;
     }
 
-    template <typename T>
-    struct type_is_container
-    {
-      static constexpr bool value = false;
-    };
+    m_default = rhs.m_default;
+    m_implicit = rhs.m_implicit;
+    m_default_value = rhs.m_default_value;
+    m_implicit_value = rhs.m_implicit_value;
+  }
 
-    template <typename T>
-    struct type_is_container<std::vector<T>>
-    {
-      static constexpr bool value = true;
-    };
-
-    template <typename T>
-    class abstract_value : public Value
-    {
-      using Self = abstract_value<T>;
-
-      public:
-      abstract_value()
-      : m_result(std::make_shared<T>())
-      , m_store(m_result.get())
-      {
-      }
-
-      explicit abstract_value(T* t)
-      : m_store(t)
-      {
-      }
-
-      ~abstract_value() override = default;
-
-      abstract_value& operator=(const abstract_value&) = default;
-
-      abstract_value(const abstract_value& rhs)
-      {
-        if (rhs.m_result)
-        {
-          m_result = std::make_shared<T>();
-          m_store = m_result.get();
-        }
-        else
-        {
-          m_store = rhs.m_store;
-        }
-
-        m_default = rhs.m_default;
-        m_implicit = rhs.m_implicit;
-        m_default_value = rhs.m_default_value;
-        m_implicit_value = rhs.m_implicit_value;
-      }
-
-      void
-      parse(const std::string& text) const override
-      {
-        parse_value(text, *m_store);
-      }
-
-      bool
-      is_container() const override
-      {
-        return type_is_container<T>::value;
-      }
-
-      void
-      parse() const override
-      {
-        parse_value(m_default_value, *m_store);
-      }
-
-      bool
-      has_default() const override
-      {
-        return m_default;
-      }
-
-      bool
-      has_implicit() const override
-      {
-        return m_implicit;
-      }
-
-      std::shared_ptr<Value>
-      default_value(const std::string& value) override
-      {
-        m_default = true;
-        m_default_value = value;
-        return shared_from_this();
-      }
-
-      std::shared_ptr<Value>
-      implicit_value(const std::string& value) override
-      {
-        m_implicit = true;
-        m_implicit_value = value;
-        return shared_from_this();
-      }
-
-      std::shared_ptr<Value>
-      no_implicit_value() override
-      {
-        m_implicit = false;
-        return shared_from_this();
-      }
-
-      std::string
-      get_default_value() const override
-      {
-        return m_default_value;
-      }
-
-      std::string
-      get_implicit_value() const override
-      {
-        return m_implicit_value;
-      }
-
-      bool
-      is_boolean() const override
-      {
-        return std::is_same<T, bool>::value;
-      }
-
-      const T&
-      get() const
-      {
-        if (m_store == nullptr)
-        {
-          return *m_result;
-        }
-        return *m_store;
-      }
-
-      protected:
-      std::shared_ptr<T> m_result{};
-      T* m_store{};
-
-      bool m_default = false;
-      bool m_implicit = false;
-
-      std::string m_default_value{};
-      std::string m_implicit_value{};
-    };
-
-    template <typename T>
-    class standard_value : public abstract_value<T>
-    {
-      public:
-      using abstract_value<T>::abstract_value;
-
-      CXXOPTS_NODISCARD
-      std::shared_ptr<Value>
-      clone() const
-      {
-        return std::make_shared<standard_value<T>>(*this);
-      }
-    };
-
-    template <>
-    class standard_value<bool> : public abstract_value<bool>
-    {
-      public:
-      ~standard_value() override = default;
-
-      standard_value()
-      {
-        set_default_and_implicit();
-      }
-
-      explicit standard_value(bool* b)
-      : abstract_value(b)
-      {
-        set_default_and_implicit();
-      }
-
-      std::shared_ptr<Value>
-      clone() const override
-      {
-        return std::make_shared<standard_value<bool>>(*this);
-      }
-
-      private:
-
-      void
-      set_default_and_implicit()
-      {
-        m_default = true;
-        m_default_value = "false";
-        m_implicit = true;
-        m_implicit_value = "true";
-      }
-    };
-  } // namespace values
-
-  template <typename T>
-  std::shared_ptr<Value>
-  value()
+  void
+  parse(const std::string& text) const override
   {
-    return std::make_shared<values::standard_value<T>>();
+    parse_value(text, *m_store);
+  }
+
+  bool
+  is_container() const override
+  {
+    return type_is_container<T>::value;
+  }
+
+  void
+  parse() const override
+  {
+    parse_value(m_default_value, *m_store);
+  }
+
+  bool
+  has_default() const override
+  {
+    return m_default;
+  }
+
+  bool
+  has_implicit() const override
+  {
+    return m_implicit;
+  }
+
+  std::shared_ptr<Value>
+  default_value(const std::string& value) override
+  {
+    m_default = true;
+    m_default_value = value;
+    return shared_from_this();
+  }
+
+  std::shared_ptr<Value>
+  implicit_value(const std::string& value) override
+  {
+    m_implicit = true;
+    m_implicit_value = value;
+    return shared_from_this();
+  }
+
+  std::shared_ptr<Value>
+  no_implicit_value() override
+  {
+    m_implicit = false;
+    return shared_from_this();
+  }
+
+  std::string
+  get_default_value() const override
+  {
+    return m_default_value;
+  }
+
+  std::string
+  get_implicit_value() const override
+  {
+    return m_implicit_value;
+  }
+
+  bool
+  is_boolean() const override
+  {
+    return std::is_same<T, bool>::value;
+  }
+
+  const T&
+  get() const
+  {
+    if (m_store == nullptr)
+    {
+      return *m_result;
+    }
+    return *m_store;
+  }
+
+  protected:
+  std::shared_ptr<T> m_result{};
+  T* m_store{};
+
+  bool m_default = false;
+  bool m_implicit = false;
+
+  std::string m_default_value{};
+  std::string m_implicit_value{};
+};
+
+template <typename T>
+class standard_value : public abstract_value<T>
+{
+  public:
+  using abstract_value<T>::abstract_value;
+
+  CXXOPTS_NODISCARD
+  std::shared_ptr<Value>
+  clone() const override
+  {
+    return std::make_shared<standard_value<T>>(*this);
+  }
+};
+
+template <>
+class standard_value<bool> : public abstract_value<bool>
+{
+  public:
+  ~standard_value() override = default;
+
+  standard_value()
+  {
+    set_default_and_implicit();
+  }
+
+  explicit standard_value(bool* b)
+  : abstract_value(b)
+  {
+    m_implicit = true;
+    m_implicit_value = "true";
+  }
+
+  std::shared_ptr<Value>
+  clone() const override
+  {
+    return std::make_shared<standard_value<bool>>(*this);
+  }
+
+  private:
+
+  void
+  set_default_and_implicit()
+  {
+    m_default = true;
+    m_default_value = "false";
+    m_implicit = true;
+    m_implicit_value = "true";
+  }
+};
+
+} // namespace values
+
+template <typename T>
+std::shared_ptr<Value>
+value()
+{
+  return std::make_shared<values::standard_value<T>>();
+}
+
+template <typename T>
+std::shared_ptr<Value>
+value(T& t)
+{
+  return std::make_shared<values::standard_value<T>>(&t);
+}
+
+class OptionAdder;
+
+CXXOPTS_NODISCARD
+inline
+const std::string&
+first_or_empty(const OptionNames& long_names)
+{
+  static const std::string empty{""};
+  return long_names.empty() ? empty : long_names.front();
+}
+
+class OptionDetails
+{
+  public:
+  OptionDetails
+  (
+    std::string short_,
+    OptionNames long_,
+    String desc,
+    std::shared_ptr<const Value> val
+  )
+  : m_short(std::move(short_))
+  , m_long(std::move(long_))
+  , m_desc(std::move(desc))
+  , m_value(std::move(val))
+  , m_count(0)
+  {
+    m_hash = std::hash<std::string>{}(first_long_name() + m_short);
+  }
+
+  OptionDetails(const OptionDetails& rhs)
+  : m_desc(rhs.m_desc)
+  , m_value(rhs.m_value->clone())
+  , m_count(rhs.m_count)
+  {
+  }
+
+  OptionDetails(OptionDetails&& rhs) = default;
+
+  CXXOPTS_NODISCARD
+  const String&
+  description() const
+  {
+    return m_desc;
+  }
+
+  CXXOPTS_NODISCARD
+  const Value&
+  value() const {
+      return *m_value;
+  }
+
+  CXXOPTS_NODISCARD
+  std::shared_ptr<Value>
+  make_storage() const
+  {
+    return m_value->clone();
+  }
+
+  CXXOPTS_NODISCARD
+  const std::string&
+  short_name() const
+  {
+    return m_short;
+  }
+
+  CXXOPTS_NODISCARD
+  const std::string&
+  first_long_name() const
+  {
+    return first_or_empty(m_long);
+  }
+
+  CXXOPTS_NODISCARD
+  const std::string&
+  essential_name() const
+  {
+    return m_long.empty() ? m_short : m_long.front();
+  }
+
+  CXXOPTS_NODISCARD
+  const OptionNames &
+  long_names() const
+  {
+    return m_long;
+  }
+
+  std::size_t
+  hash() const
+  {
+    return m_hash;
+  }
+
+  private:
+  std::string m_short{};
+  OptionNames m_long{};
+  String m_desc{};
+  std::shared_ptr<const Value> m_value{};
+  int m_count;
+
+  std::size_t m_hash{};
+};
+
+struct HelpOptionDetails
+{
+  std::string s;
+  OptionNames l;
+  String desc;
+  bool has_default;
+  std::string default_value;
+  bool has_implicit;
+  std::string implicit_value;
+  std::string arg_help;
+  bool is_container;
+  bool is_boolean;
+};
+
+struct HelpGroupDetails
+{
+  std::string name{};
+  std::string description{};
+  std::vector<HelpOptionDetails> options{};
+};
+
+class OptionValue
+{
+  public:
+  void
+  parse
+  (
+    const std::shared_ptr<const OptionDetails>& details,
+    const std::string& text
+  )
+  {
+    ensure_value(details);
+    ++m_count;
+    m_value->parse(text);
+    m_long_names = &details->long_names();
+  }
+
+  void
+  parse_default(const std::shared_ptr<const OptionDetails>& details)
+  {
+    ensure_value(details);
+    m_default = true;
+    m_long_names = &details->long_names();
+    m_value->parse();
+  }
+
+  void
+  parse_no_value(const std::shared_ptr<const OptionDetails>& details)
+  {
+    m_long_names = &details->long_names();
+  }
+
+#if defined(CXXOPTS_NULL_DEREF_IGNORE)
+CXXOPTS_DIAGNOSTIC_PUSH
+CXXOPTS_IGNORE_WARNING("-Wnull-dereference")
+#endif
+
+  CXXOPTS_NODISCARD
+  std::size_t
+  count() const noexcept
+  {
+    return m_count;
+  }
+
+#if defined(CXXOPTS_NULL_DEREF_IGNORE)
+CXXOPTS_DIAGNOSTIC_POP
+#endif
+
+  // TODO: maybe default options should count towards the number of arguments
+  CXXOPTS_NODISCARD
+  bool
+  has_default() const noexcept
+  {
+    return m_default;
   }
 
   template <typename T>
-  std::shared_ptr<Value>
-  value(T& t)
+  const T&
+  as() const
   {
-    return std::make_shared<values::standard_value<T>>(&t);
+    if (m_value == nullptr) {
+        throw_or_mimic<exceptions::option_has_no_value>(
+            m_long_names == nullptr ? "" : first_or_empty(*m_long_names));
+    }
+
+    return CXXOPTS_RTTI_CAST<const values::standard_value<T>&>(*m_value).get();
   }
 
-  class OptionAdder;
+  private:
+  void
+  ensure_value(const std::shared_ptr<const OptionDetails>& details)
+  {
+    if (m_value == nullptr)
+    {
+      m_value = details->make_storage();
+    }
+  }
 
-  class OptionDetails
+
+  const OptionNames * m_long_names = nullptr;
+  // Holding this pointer is safe, since OptionValue's only exist in key-value pairs,
+  // where the key has the string we point to.
+  std::shared_ptr<Value> m_value{};
+  std::size_t m_count = 0;
+  bool m_default = false;
+};
+
+class KeyValue
+{
+  public:
+  KeyValue(std::string key_, std::string value_) noexcept
+  : m_key(std::move(key_))
+  , m_value(std::move(value_))
+  {
+  }
+
+  CXXOPTS_NODISCARD
+  const std::string&
+  key() const
+  {
+    return m_key;
+  }
+
+  CXXOPTS_NODISCARD
+  const std::string&
+  value() const
+  {
+    return m_value;
+  }
+
+  template <typename T>
+  T
+  as() const
+  {
+    T result;
+    values::parse_value(m_value, result);
+    return result;
+  }
+
+  private:
+  std::string m_key;
+  std::string m_value;
+};
+
+using ParsedHashMap = std::unordered_map<std::size_t, OptionValue>;
+using NameHashMap = std::unordered_map<std::string, std::size_t>;
+
+class ParseResult
+{
+  public:
+  class Iterator
   {
     public:
-    OptionDetails
-    (
-      std::string short_,
-      std::string long_,
-      String desc,
-      std::shared_ptr<const Value> val
-    )
-    : m_short(std::move(short_))
-    , m_long(std::move(long_))
-    , m_desc(std::move(desc))
-    , m_value(std::move(val))
-    , m_count(0)
+    using iterator_category = std::forward_iterator_tag;
+    using value_type = KeyValue;
+    using difference_type = void;
+    using pointer = const KeyValue*;
+    using reference = const KeyValue&;
+
+    Iterator() = default;
+    Iterator(const Iterator&) = default;
+
+// GCC complains about m_iter not being initialised in the member
+// initializer list
+CXXOPTS_DIAGNOSTIC_PUSH
+CXXOPTS_IGNORE_WARNING("-Weffc++")
+    Iterator(const ParseResult *pr, bool end=false)
+    : m_pr(pr)
     {
-      m_hash = std::hash<std::string>{}(m_long + m_short);
-    }
-
-    OptionDetails(const OptionDetails& rhs)
-    : m_desc(rhs.m_desc)
-    , m_count(rhs.m_count)
-    {
-      m_value = rhs.m_value->clone();
-    }
-
-    OptionDetails(OptionDetails&& rhs) = default;
-
-    CXXOPTS_NODISCARD
-    const String&
-    description() const
-    {
-      return m_desc;
-    }
-
-    CXXOPTS_NODISCARD
-    const Value&
-    value() const {
-        return *m_value;
-    }
-
-    CXXOPTS_NODISCARD
-    std::shared_ptr<Value>
-    make_storage() const
-    {
-      return m_value->clone();
-    }
-
-    CXXOPTS_NODISCARD
-    const std::string&
-    short_name() const
-    {
-      return m_short;
-    }
-
-    CXXOPTS_NODISCARD
-    const std::string&
-    long_name() const
-    {
-      return m_long;
-    }
-
-    size_t
-    hash() const
-    {
-      return m_hash;
-    }
-
-    private:
-    std::string m_short{};
-    std::string m_long{};
-    String m_desc{};
-    std::shared_ptr<const Value> m_value{};
-    int m_count;
-
-    size_t m_hash{};
-  };
-
-  struct HelpOptionDetails
-  {
-    std::string s;
-    std::string l;
-    String desc;
-    bool has_default;
-    std::string default_value;
-    bool has_implicit;
-    std::string implicit_value;
-    std::string arg_help;
-    bool is_container;
-    bool is_boolean;
-  };
-
-  struct HelpGroupDetails
-  {
-    std::string name{};
-    std::string description{};
-    std::vector<HelpOptionDetails> options{};
-  };
-
-  class OptionValue
-  {
-    public:
-    void
-    parse
-    (
-      const std::shared_ptr<const OptionDetails>& details,
-      const std::string& text
-    )
-    {
-      ensure_value(details);
-      ++m_count;
-      m_value->parse(text);
-      m_long_name = &details->long_name();
-    }
-
-    void
-    parse_default(const std::shared_ptr<const OptionDetails>& details)
-    {
-      ensure_value(details);
-      m_default = true;
-      m_long_name = &details->long_name();
-      m_value->parse();
-    }
-
-    CXXOPTS_NODISCARD
-    size_t
-    count() const noexcept
-    {
-      return m_count;
-    }
-
-    // TODO: maybe default options should count towards the number of arguments
-    CXXOPTS_NODISCARD
-    bool
-    has_default() const noexcept
-    {
-      return m_default;
-    }
-
-    template <typename T>
-    const T&
-    as() const
-    {
-      if (m_value == nullptr) {
-          throw_or_mimic<option_has_no_value_exception>(
-              m_long_name == nullptr ? "" : *m_long_name);
-      }
-
-#ifdef CXXOPTS_NO_RTTI
-      return static_cast<const values::standard_value<T>&>(*m_value).get();
-#else
-      return dynamic_cast<const values::standard_value<T>&>(*m_value).get();
-#endif
-    }
-
-    private:
-    void
-    ensure_value(const std::shared_ptr<const OptionDetails>& details)
-    {
-      if (m_value == nullptr)
+      if (end)
       {
-        m_value = details->make_storage();
-      }
-    }
-
-
-    const std::string* m_long_name = nullptr;
-    // Holding this pointer is safe, since OptionValue's only exist in key-value pairs,
-    // where the key has the string we point to.
-    std::shared_ptr<Value> m_value{};
-    size_t m_count = 0;
-    bool m_default = false;
-  };
-
-  class KeyValue
-  {
-    public:
-    KeyValue(std::string key_, std::string value_)
-    : m_key(std::move(key_))
-    , m_value(std::move(value_))
-    {
-    }
-
-    CXXOPTS_NODISCARD
-    const std::string&
-    key() const
-    {
-      return m_key;
-    }
-
-    CXXOPTS_NODISCARD
-    const std::string&
-    value() const
-    {
-      return m_value;
-    }
-
-    template <typename T>
-    T
-    as() const
-    {
-      T result;
-      values::parse_value(m_value, result);
-      return result;
-    }
-
-    private:
-    std::string m_key;
-    std::string m_value;
-  };
-
-  using ParsedHashMap = std::unordered_map<size_t, OptionValue>;
-  using NameHashMap = std::unordered_map<std::string, size_t>;
-
-  class ParseResult
-  {
-    public:
-
-    ParseResult() {}
-
-    ParseResult(const ParseResult&) = default;
-
-    ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector<KeyValue> sequential, std::vector<std::string>&& unmatched_args)
-    : m_keys(std::move(keys))
-    , m_values(std::move(values))
-    , m_sequential(std::move(sequential))
-    , m_unmatched(std::move(unmatched_args))
-    {
-    }
-
-    ParseResult& operator=(ParseResult&&) = default;
-    ParseResult& operator=(const ParseResult&) = default;
-
-    size_t
-    count(const std::string& o) const
-    {
-      auto iter = m_keys.find(o);
-      if (iter == m_keys.end())
-      {
-        return 0;
-      }
-
-      auto viter = m_values.find(iter->second);
-
-      if (viter == m_values.end())
-      {
-        return 0;
-      }
-
-      return viter->second.count();
-    }
-
-    const OptionValue&
-    operator[](const std::string& option) const
-    {
-      auto iter = m_keys.find(option);
-
-      if (iter == m_keys.end())
-      {
-        throw_or_mimic<option_not_present_exception>(option);
-      }
-
-      auto viter = m_values.find(iter->second);
-
-      if (viter == m_values.end())
-      {
-        throw_or_mimic<option_not_present_exception>(option);
-      }
-
-      return viter->second;
-    }
-
-    const std::vector<KeyValue>&
-    arguments() const
-    {
-      return m_sequential;
-    }
-
-    const std::vector<std::string>&
-    unmatched() const
-    {
-      return m_unmatched;
-    }
-
-    private:
-    NameHashMap m_keys{};
-    ParsedHashMap m_values{};
-    std::vector<KeyValue> m_sequential{};
-    std::vector<std::string> m_unmatched{};
-  };
-
-  struct Option
-  {
-    Option
-    (
-      std::string opts,
-      std::string desc,
-      std::shared_ptr<const Value>  value = ::cxxopts::value<bool>(),
-      std::string arg_help = ""
-    )
-    : opts_(std::move(opts))
-    , desc_(std::move(desc))
-    , value_(std::move(value))
-    , arg_help_(std::move(arg_help))
-    {
-    }
-
-    std::string opts_;
-    std::string desc_;
-    std::shared_ptr<const Value> value_;
-    std::string arg_help_;
-  };
-
-  using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>;
-  using PositionalList = std::vector<std::string>;
-  using PositionalListIterator = PositionalList::const_iterator;
-
-  class OptionParser
-  {
-    public:
-    OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised)
-    : m_options(options)
-    , m_positional(positional)
-    , m_allow_unrecognised(allow_unrecognised)
-    {
-    }
-
-    ParseResult
-    parse(int argc, const char* const* argv);
-
-    bool
-    consume_positional(const std::string& a, PositionalListIterator& next);
-
-    void
-    checked_parse_arg
-    (
-      int argc,
-      const char* const* argv,
-      int& current,
-      const std::shared_ptr<OptionDetails>& value,
-      const std::string& name
-    );
-
-    void
-    add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg);
-
-    void
-    parse_option
-    (
-      const std::shared_ptr<OptionDetails>& value,
-      const std::string& name,
-      const std::string& arg = ""
-    );
-
-    void
-    parse_default(const std::shared_ptr<OptionDetails>& details);
-
-    private:
-
-    void finalise_aliases();
-
-    const OptionMap& m_options;
-    const PositionalList& m_positional;
-
-    std::vector<KeyValue> m_sequential{};
-    bool m_allow_unrecognised;
-
-    ParsedHashMap m_parsed{};
-    NameHashMap m_keys{};
-  };
-
-  class Options
-  {
-    public:
-
-    explicit Options(std::string program, std::string help_string = "")
-    : m_program(std::move(program))
-    , m_help_string(toLocalString(std::move(help_string)))
-    , m_custom_help("[OPTION...]")
-    , m_positional_help("positional parameters")
-    , m_show_positional(false)
-    , m_allow_unrecognised(false)
-    , m_options(std::make_shared<OptionMap>())
-    {
-    }
-
-    Options&
-    positional_help(std::string help_text)
-    {
-      m_positional_help = std::move(help_text);
-      return *this;
-    }
-
-    Options&
-    custom_help(std::string help_text)
-    {
-      m_custom_help = std::move(help_text);
-      return *this;
-    }
-
-    Options&
-    show_positional_help()
-    {
-      m_show_positional = true;
-      return *this;
-    }
-
-    Options&
-    allow_unrecognised_options()
-    {
-      m_allow_unrecognised = true;
-      return *this;
-    }
-
-    ParseResult
-    parse(int argc, const char* const* argv);
-
-    OptionAdder
-    add_options(std::string group = "");
-
-    void
-    add_options
-    (
-      const std::string& group,
-      std::initializer_list<Option> options
-    );
-
-    void
-    add_option
-    (
-      const std::string& group,
-      const Option& option
-    );
-
-    void
-    add_option
-    (
-      const std::string& group,
-      const std::string& s,
-      const std::string& l,
-      std::string desc,
-      const std::shared_ptr<const Value>& value,
-      std::string arg_help
-    );
-
-    //parse positional arguments into the given option
-    void
-    parse_positional(std::string option);
-
-    void
-    parse_positional(std::vector<std::string> options);
-
-    void
-    parse_positional(std::initializer_list<std::string> options);
-
-    template <typename Iterator>
-    void
-    parse_positional(Iterator begin, Iterator end) {
-      parse_positional(std::vector<std::string>{begin, end});
-    }
-
-    std::string
-    help(const std::vector<std::string>& groups = {}) const;
-
-    std::vector<std::string>
-    groups() const;
-
-    const HelpGroupDetails&
-    group_help(const std::string& group) const;
-
-    private:
-
-    void
-    add_one_option
-    (
-      const std::string& option,
-      const std::shared_ptr<OptionDetails>& details
-    );
-
-    String
-    help_one_group(const std::string& group) const;
-
-    void
-    generate_group_help
-    (
-      String& result,
-      const std::vector<std::string>& groups
-    ) const;
-
-    void
-    generate_all_groups_help(String& result) const;
-
-    std::string m_program{};
-    String m_help_string{};
-    std::string m_custom_help{};
-    std::string m_positional_help{};
-    bool m_show_positional;
-    bool m_allow_unrecognised;
-
-    std::shared_ptr<OptionMap> m_options;
-    std::vector<std::string> m_positional{};
-    std::unordered_set<std::string> m_positional_set{};
-
-    //mapping from groups to help options
-    std::map<std::string, HelpGroupDetails> m_help{};
-
-    std::list<OptionDetails> m_option_list{};
-    std::unordered_map<std::string, decltype(m_option_list)::iterator> m_option_map{};
-  };
-
-  class OptionAdder
-  {
-    public:
-
-    OptionAdder(Options& options, std::string group)
-    : m_options(options), m_group(std::move(group))
-    {
-    }
-
-    OptionAdder&
-    operator()
-    (
-      const std::string& opts,
-      const std::string& desc,
-      const std::shared_ptr<const Value>& value
-        = ::cxxopts::value<bool>(),
-      std::string arg_help = ""
-    );
-
-    private:
-    Options& m_options;
-    std::string m_group;
-  };
-
-  namespace
-  {
-    constexpr int OPTION_LONGEST = 30;
-    constexpr int OPTION_DESC_GAP = 2;
-
-    std::basic_regex<char> option_matcher
-      ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)");
-
-    std::basic_regex<char> option_specifier
-      ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?");
-
-    String
-    format_option
-    (
-      const HelpOptionDetails& o
-    )
-    {
-      const auto& s = o.s;
-      const auto& l = o.l;
-
-      String result = "  ";
-
-      if (!s.empty())
-      {
-        result += "-" + toLocalString(s);
-        if (!l.empty())
-        {
-          result += ",";
-        }
+        m_sequential = false;
+        m_iter = m_pr->m_defaults.end();
       }
       else
       {
-        result += "   ";
-      }
+        m_sequential = true;
+        m_iter = m_pr->m_sequential.begin();
 
-      if (!l.empty())
-      {
-        result += " --" + toLocalString(l);
-      }
-
-      auto arg = !o.arg_help.empty() ? toLocalString(o.arg_help) : "arg";
-
-      if (!o.is_boolean)
-      {
-        if (o.has_implicit)
+        if (m_iter == m_pr->m_sequential.end())
         {
-          result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]";
-        }
-        else
-        {
-          result += " " + arg;
+          m_sequential = false;
+          m_iter = m_pr->m_defaults.begin();
         }
       }
-
-      return result;
     }
+CXXOPTS_DIAGNOSTIC_POP
 
-    String
-    format_description
-    (
-      const HelpOptionDetails& o,
-      size_t start,
-      size_t width
-    )
+    Iterator& operator++()
     {
-      auto desc = o.desc;
-
-      if (o.has_default && (!o.is_boolean || o.default_value != "false"))
+      ++m_iter;
+      if(m_sequential && m_iter == m_pr->m_sequential.end())
       {
-        if(!o.default_value.empty())
-        {
-          desc += toLocalString(" (default: " + o.default_value + ")");
-        }
-        else
-        {
-          desc += toLocalString(" (default: \"\")");
-        }
+        m_sequential = false;
+        m_iter = m_pr->m_defaults.begin();
+        return *this;
       }
-
-      String result;
-
-      auto current = std::begin(desc);
-      auto startLine = current;
-      auto lastSpace = current;
-
-      auto size = size_t{};
-
-      while (current != std::end(desc))
-      {
-        if (*current == ' ')
-        {
-          lastSpace = current;
-        }
-
-        if (*current == '\n')
-        {
-          startLine = current + 1;
-          lastSpace = startLine;
-        }
-        else if (size > width)
-        {
-          if (lastSpace == startLine)
-          {
-            stringAppend(result, startLine, current + 1);
-            stringAppend(result, "\n");
-            stringAppend(result, start, ' ');
-            startLine = current + 1;
-            lastSpace = startLine;
-          }
-          else
-          {
-            stringAppend(result, startLine, lastSpace);
-            stringAppend(result, "\n");
-            stringAppend(result, start, ' ');
-            startLine = lastSpace + 1;
-            lastSpace = startLine;
-          }
-          size = 0;
-        }
-        else
-        {
-          ++size;
-        }
-
-        ++current;
-      }
-
-      //append whatever is left
-      stringAppend(result, startLine, current);
-
-      return result;
+      return *this;
     }
-  } // namespace
+
+    Iterator operator++(int)
+    {
+      Iterator retval = *this;
+      ++(*this);
+      return retval;
+    }
+
+    bool operator==(const Iterator& other) const
+    {
+      return (m_sequential == other.m_sequential) && (m_iter == other.m_iter);
+    }
+
+    bool operator!=(const Iterator& other) const
+    {
+      return !(*this == other);
+    }
+
+    const KeyValue& operator*()
+    {
+      return *m_iter;
+    }
+
+    const KeyValue* operator->()
+    {
+      return m_iter.operator->();
+    }
+
+    private:
+    const ParseResult* m_pr;
+    std::vector<KeyValue>::const_iterator m_iter;
+    bool m_sequential = true;
+  };
+
+  ParseResult() = default;
+  ParseResult(const ParseResult&) = default;
+
+  ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector<KeyValue> sequential,
+          std::vector<KeyValue> default_opts, std::vector<std::string>&& unmatched_args)
+  : m_keys(std::move(keys))
+  , m_values(std::move(values))
+  , m_sequential(std::move(sequential))
+  , m_defaults(std::move(default_opts))
+  , m_unmatched(std::move(unmatched_args))
+  {
+  }
+
+  ParseResult& operator=(ParseResult&&) = default;
+  ParseResult& operator=(const ParseResult&) = default;
+
+  Iterator
+  begin() const
+  {
+    return Iterator(this);
+  }
+
+  Iterator
+  end() const
+  {
+    return Iterator(this, true);
+  }
+
+  std::size_t
+  count(const std::string& o) const
+  {
+    auto iter = m_keys.find(o);
+    if (iter == m_keys.end())
+    {
+      return 0;
+    }
+
+    auto viter = m_values.find(iter->second);
+
+    if (viter == m_values.end())
+    {
+      return 0;
+    }
+
+    return viter->second.count();
+  }
+
+  const OptionValue&
+  operator[](const std::string& option) const
+  {
+    auto iter = m_keys.find(option);
+
+    if (iter == m_keys.end())
+    {
+      throw_or_mimic<exceptions::requested_option_not_present>(option);
+    }
+
+    auto viter = m_values.find(iter->second);
+
+    if (viter == m_values.end())
+    {
+      throw_or_mimic<exceptions::requested_option_not_present>(option);
+    }
+
+    return viter->second;
+  }
+
+  const std::vector<KeyValue>&
+  arguments() const
+  {
+    return m_sequential;
+  }
+
+  const std::vector<std::string>&
+  unmatched() const
+  {
+    return m_unmatched;
+  }
+
+  const std::vector<KeyValue>&
+  defaults() const
+  {
+    return m_defaults;
+  }
+
+  const std::string
+  arguments_string() const
+  {
+    std::string result;
+    for(const auto& kv: m_sequential)
+    {
+      result += kv.key() + " = " + kv.value() + "\n";
+    }
+    for(const auto& kv: m_defaults)
+    {
+      result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n";
+    }
+    return result;
+  }
+
+  private:
+  NameHashMap m_keys{};
+  ParsedHashMap m_values{};
+  std::vector<KeyValue> m_sequential{};
+  std::vector<KeyValue> m_defaults{};
+  std::vector<std::string> m_unmatched{};
+};
+
+struct Option
+{
+  Option
+  (
+    std::string opts,
+    std::string desc,
+    std::shared_ptr<const Value>  value = ::cxxopts::value<bool>(),
+    std::string arg_help = ""
+  )
+  : opts_(std::move(opts))
+  , desc_(std::move(desc))
+  , value_(std::move(value))
+  , arg_help_(std::move(arg_help))
+  {
+  }
+
+  std::string opts_;
+  std::string desc_;
+  std::shared_ptr<const Value> value_;
+  std::string arg_help_;
+};
+
+using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>;
+using PositionalList = std::vector<std::string>;
+using PositionalListIterator = PositionalList::const_iterator;
+
+class OptionParser
+{
+  public:
+  OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised)
+  : m_options(options)
+  , m_positional(positional)
+  , m_allow_unrecognised(allow_unrecognised)
+  {
+  }
+
+  ParseResult
+  parse(int argc, const char* const* argv);
+
+  bool
+  consume_positional(const std::string& a, PositionalListIterator& next);
+
+  void
+  checked_parse_arg
+  (
+    int argc,
+    const char* const* argv,
+    int& current,
+    const std::shared_ptr<OptionDetails>& value,
+    const std::string& name
+  );
+
+  void
+  add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg);
+
+  void
+  parse_option
+  (
+    const std::shared_ptr<OptionDetails>& value,
+    const std::string& name,
+    const std::string& arg = ""
+  );
+
+  void
+  parse_default(const std::shared_ptr<OptionDetails>& details);
+
+  void
+  parse_no_value(const std::shared_ptr<OptionDetails>& details);
+
+  private:
+
+  void finalise_aliases();
+
+  const OptionMap& m_options;
+  const PositionalList& m_positional;
+
+  std::vector<KeyValue> m_sequential{};
+  std::vector<KeyValue> m_defaults{};
+  bool m_allow_unrecognised;
+
+  ParsedHashMap m_parsed{};
+  NameHashMap m_keys{};
+};
+
+class Options
+{
+  public:
+
+  explicit Options(std::string program_name, std::string help_string = "")
+  : m_program(std::move(program_name))
+  , m_help_string(toLocalString(std::move(help_string)))
+  , m_custom_help("[OPTION...]")
+  , m_positional_help("positional parameters")
+  , m_show_positional(false)
+  , m_allow_unrecognised(false)
+  , m_width(76)
+  , m_tab_expansion(false)
+  , m_options(std::make_shared<OptionMap>())
+  {
+  }
+
+  Options&
+  positional_help(std::string help_text)
+  {
+    m_positional_help = std::move(help_text);
+    return *this;
+  }
+
+  Options&
+  custom_help(std::string help_text)
+  {
+    m_custom_help = std::move(help_text);
+    return *this;
+  }
+
+  Options&
+  show_positional_help()
+  {
+    m_show_positional = true;
+    return *this;
+  }
+
+  Options&
+  allow_unrecognised_options()
+  {
+    m_allow_unrecognised = true;
+    return *this;
+  }
+
+  Options&
+  set_width(std::size_t width)
+  {
+    m_width = width;
+    return *this;
+  }
+
+  Options&
+  set_tab_expansion(bool expansion=true)
+  {
+    m_tab_expansion = expansion;
+    return *this;
+  }
+
+  ParseResult
+  parse(int argc, const char* const* argv);
+
+  OptionAdder
+  add_options(std::string group = "");
+
+  void
+  add_options
+  (
+    const std::string& group,
+    std::initializer_list<Option> options
+  );
+
+  void
+  add_option
+  (
+    const std::string& group,
+    const Option& option
+  );
+
+  void
+  add_option
+  (
+    const std::string& group,
+    const std::string& s,
+    const OptionNames& l,
+    std::string desc,
+    const std::shared_ptr<const Value>& value,
+    std::string arg_help
+  );
+
+  void
+  add_option
+  (
+    const std::string& group,
+    const std::string& short_name,
+    const std::string& single_long_name,
+    std::string desc,
+    const std::shared_ptr<const Value>& value,
+    std::string arg_help
+  )
+  {
+    OptionNames long_names;
+    long_names.emplace_back(single_long_name);
+    add_option(group, short_name, long_names, desc, value, arg_help);
+  }
+
+  //parse positional arguments into the given option
+  void
+  parse_positional(std::string option);
+
+  void
+  parse_positional(std::vector<std::string> options);
+
+  void
+  parse_positional(std::initializer_list<std::string> options);
+
+  template <typename Iterator>
+  void
+  parse_positional(Iterator begin, Iterator end) {
+    parse_positional(std::vector<std::string>{begin, end});
+  }
+
+  std::string
+  help(const std::vector<std::string>& groups = {}, bool print_usage=true) const;
+
+  std::vector<std::string>
+  groups() const;
+
+  const HelpGroupDetails&
+  group_help(const std::string& group) const;
+
+  const std::string& program() const
+  {
+    return m_program;
+  }
+
+  private:
+
+  void
+  add_one_option
+  (
+    const std::string& option,
+    const std::shared_ptr<OptionDetails>& details
+  );
+
+  String
+  help_one_group(const std::string& group) const;
+
+  void
+  generate_group_help
+  (
+    String& result,
+    const std::vector<std::string>& groups
+  ) const;
+
+  void
+  generate_all_groups_help(String& result) const;
+
+  std::string m_program{};
+  String m_help_string{};
+  std::string m_custom_help{};
+  std::string m_positional_help{};
+  bool m_show_positional;
+  bool m_allow_unrecognised;
+  std::size_t m_width;
+  bool m_tab_expansion;
+
+  std::shared_ptr<OptionMap> m_options;
+  std::vector<std::string> m_positional{};
+  std::unordered_set<std::string> m_positional_set{};
+
+  //mapping from groups to help options
+  std::map<std::string, HelpGroupDetails> m_help{};
+};
+
+class OptionAdder
+{
+  public:
+
+  OptionAdder(Options& options, std::string group)
+  : m_options(options), m_group(std::move(group))
+  {
+  }
+
+  OptionAdder&
+  operator()
+  (
+    const std::string& opts,
+    const std::string& desc,
+    const std::shared_ptr<const Value>& value
+      = ::cxxopts::value<bool>(),
+    std::string arg_help = ""
+  );
+
+  private:
+  Options& m_options;
+  std::string m_group;
+};
+
+namespace {
+constexpr std::size_t OPTION_LONGEST = 30;
+constexpr std::size_t OPTION_DESC_GAP = 2;
+
+String
+format_option
+(
+  const HelpOptionDetails& o
+)
+{
+  const auto& s = o.s;
+  const auto& l = first_or_empty(o.l);
+
+  String result = "  ";
+
+  if (!s.empty())
+  {
+    result += "-" + toLocalString(s);
+    if (!l.empty())
+    {
+      result += ",";
+    }
+  }
+  else
+  {
+    result += "   ";
+  }
+
+  if (!l.empty())
+  {
+    result += " --" + toLocalString(l);
+  }
+
+  auto arg = !o.arg_help.empty() ? toLocalString(o.arg_help) : "arg";
+
+  if (!o.is_boolean)
+  {
+    if (o.has_implicit)
+    {
+      result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]";
+    }
+    else
+    {
+      result += " " + arg;
+    }
+  }
+
+  return result;
+}
+
+String
+format_description
+(
+  const HelpOptionDetails& o,
+  std::size_t start,
+  std::size_t allowed,
+  bool tab_expansion
+)
+{
+  auto desc = o.desc;
+
+  if (o.has_default && (!o.is_boolean || o.default_value != "false"))
+  {
+    if(!o.default_value.empty())
+    {
+      desc += toLocalString(" (default: " + o.default_value + ")");
+    }
+    else
+    {
+      desc += toLocalString(" (default: \"\")");
+    }
+  }
+
+  String result;
+
+  if (tab_expansion)
+  {
+    String desc2;
+    auto size = std::size_t{ 0 };
+    for (auto c = std::begin(desc); c != std::end(desc); ++c)
+    {
+      if (*c == '\n')
+      {
+        desc2 += *c;
+        size = 0;
+      }
+      else if (*c == '\t')
+      {
+        auto skip = 8 - size % 8;
+        stringAppend(desc2, skip, ' ');
+        size += skip;
+      }
+      else
+      {
+        desc2 += *c;
+        ++size;
+      }
+    }
+    desc = desc2;
+  }
+
+  desc += " ";
+
+  auto current = std::begin(desc);
+  auto previous = current;
+  auto startLine = current;
+  auto lastSpace = current;
+
+  auto size = std::size_t{};
+
+  bool appendNewLine;
+  bool onlyWhiteSpace = true;
+
+  while (current != std::end(desc))
+  {
+    appendNewLine = false;
+    if (*previous == ' ' || *previous == '\t')
+    {
+      lastSpace = current;
+    }
+    if (*current != ' ' && *current != '\t')
+    {
+      onlyWhiteSpace = false;
+    }
+
+    while (*current == '\n')
+    {
+      previous = current;
+      ++current;
+      appendNewLine = true;
+    }
+
+    if (!appendNewLine && size >= allowed)
+    {
+      if (lastSpace != startLine)
+      {
+        current = lastSpace;
+        previous = current;
+      }
+      appendNewLine = true;
+    }
+
+    if (appendNewLine)
+    {
+      stringAppend(result, startLine, current);
+      startLine = current;
+      lastSpace = current;
+
+      if (*previous != '\n')
+      {
+        stringAppend(result, "\n");
+      }
+
+      stringAppend(result, start, ' ');
+
+      if (*previous != '\n')
+      {
+        stringAppend(result, lastSpace, current);
+      }
+
+      onlyWhiteSpace = true;
+      size = 0;
+    }
+
+    previous = current;
+    ++current;
+    ++size;
+  }
+
+  //append whatever is left but ignore whitespace
+  if (!onlyWhiteSpace)
+  {
+    stringAppend(result, startLine, previous);
+  }
+
+  return result;
+}
+
+} // namespace
 
 inline
 void
@@ -1711,43 +2220,31 @@
   std::string arg_help
 )
 {
-  std::match_results<const char*> result;
-  std::regex_match(opts.c_str(), result, option_specifier);
-
-  if (result.empty())
-  {
-    throw_or_mimic<invalid_option_format_error>(opts);
-  }
-
-  const auto& short_match = result[2];
-  const auto& long_match = result[3];
-
-  if (!short_match.length() && !long_match.length())
-  {
-    throw_or_mimic<invalid_option_format_error>(opts);
-  } else if (long_match.length() == 1 && short_match.length())
-  {
-    throw_or_mimic<invalid_option_format_error>(opts);
-  }
-
-  auto option_names = []
-  (
-    const std::sub_match<const char*>& short_,
-    const std::sub_match<const char*>& long_
-  )
-  {
-    if (long_.length() == 1)
-    {
-      return std::make_tuple(long_.str(), short_.str());
-    }
-    return std::make_tuple(short_.str(), long_.str());
-  }(short_match, long_match);
+  OptionNames option_names = values::parser_tool::split_option_names(opts);
+    // Note: All names will be non-empty; but we must separate the short
+    // (length-1) and longer names
+  std::string short_name {""};
+  auto first_short_name_iter =
+    std::partition(option_names.begin(), option_names.end(),
+      [&](const std::string& name) { return name.length() > 1; }
+    );
+  auto num_length_1_names = (option_names.end() - first_short_name_iter);
+  switch(num_length_1_names) {
+  case 1:
+    short_name = *first_short_name_iter;
+    option_names.erase(first_short_name_iter);
+    break;
+  case 0:
+    break;
+  default:
+    throw_or_mimic<exceptions::invalid_option_format>(opts);
+  };
 
   m_options.add_option
   (
     m_group,
-    std::get<0>(option_names),
-    std::get<1>(option_names),
+    short_name,
+    option_names,
     desc,
     value,
     std::move(arg_help)
@@ -1763,6 +2260,15 @@
   // TODO: remove the duplicate code here
   auto& store = m_parsed[details->hash()];
   store.parse_default(details);
+  m_defaults.emplace_back(details->essential_name(), details->value().get_default_value());
+}
+
+inline
+void
+OptionParser::parse_no_value(const std::shared_ptr<OptionDetails>& details)
+{
+  auto& store = m_parsed[details->hash()];
+  store.parse_no_value(details);
 }
 
 inline
@@ -1778,7 +2284,7 @@
   auto& result = m_parsed[hash];
   result.parse(value, arg);
 
-  m_sequential.emplace_back(value->long_name(), arg);
+  m_sequential.emplace_back(value->essential_name(), arg);
 }
 
 inline
@@ -1800,7 +2306,7 @@
     }
     else
     {
-      throw_or_mimic<missing_argument_exception>(name);
+      throw_or_mimic<exceptions::missing_argument>(name);
     }
   }
   else
@@ -1833,9 +2339,9 @@
     auto iter = m_options.find(*next);
     if (iter != m_options.end())
     {
-      auto& result = m_parsed[iter->second->hash()];
       if (!iter->second->value().is_container())
       {
+        auto& result = m_parsed[iter->second->hash()];
         if (result.count() == 0)
         {
           add_to_option(iter, *next, a);
@@ -1848,7 +2354,7 @@
       add_to_option(iter, *next, a);
       return true;
     }
-    throw_or_mimic<option_not_exists_exception>(*next);
+    throw_or_mimic<exceptions::no_such_option>(*next);
   }
 
   return false;
@@ -1891,7 +2397,7 @@
 {
   int current = 1;
   bool consume_remaining = false;
-  PositionalListIterator next_positional = m_positional.begin();
+  auto next_positional = m_positional.begin();
 
   std::vector<std::string> unmatched;
 
@@ -1903,18 +2409,18 @@
       ++current;
       break;
     }
+    bool matched = false;
+    values::parser_tool::ArguDesc argu_desc =
+        values::parser_tool::ParseArgument(argv[current], matched);
 
-    std::match_results<const char*> result;
-    std::regex_match(argv[current], result, option_matcher);
-
-    if (result.empty())
+    if (!matched)
     {
       //not a flag
 
       // but if it starts with a `-`, then it's an error
       if (argv[current][0] == '-' && argv[current][1] != '\0') {
         if (!m_allow_unrecognised) {
-          throw_or_mimic<option_syntax_exception>(argv[current]);
+          throw_or_mimic<exceptions::invalid_option_syntax>(argv[current]);
         }
       }
 
@@ -1925,16 +2431,16 @@
       }
       else
       {
-        unmatched.push_back(argv[current]);
+        unmatched.emplace_back(argv[current]);
       }
       //if we return from here then it was parsed successfully, so continue
     }
     else
     {
       //short or long option?
-      if (result[4].length() != 0)
+      if (argu_desc.grouping)
       {
-        const std::string& s = result[4];
+        const std::string& s = argu_desc.arg_name;
 
         for (std::size_t i = 0; i != s.size(); ++i)
         {
@@ -1945,10 +2451,11 @@
           {
             if (m_allow_unrecognised)
             {
+              unmatched.push_back(std::string("-") + s[i]);
               continue;
             }
             //error
-            throw_or_mimic<option_not_exists_exception>(name);
+            throw_or_mimic<exceptions::no_such_option>(name);
           }
 
           auto value = iter->second;
@@ -1962,16 +2469,22 @@
           {
             parse_option(value, name, value->value().get_implicit_value());
           }
+          else if (i + 1 < s.size())
+          {
+            std::string arg_value = s.substr(i + 1);
+            parse_option(value, name, arg_value);
+            break;
+          }
           else
           {
             //error
-            throw_or_mimic<option_requires_argument_exception>(name);
+            throw_or_mimic<exceptions::option_requires_argument>(name);
           }
         }
       }
-      else if (result[1].length() != 0)
+      else if (argu_desc.arg_name.length() != 0)
       {
-        const std::string& name = result[1];
+        const std::string& name = argu_desc.arg_name;
 
         auto iter = m_options.find(name);
 
@@ -1980,22 +2493,22 @@
           if (m_allow_unrecognised)
           {
             // keep unrecognised options in argument list, skip to next argument
-            unmatched.push_back(argv[current]);
+            unmatched.emplace_back(argv[current]);
             ++current;
             continue;
           }
           //error
-          throw_or_mimic<option_not_exists_exception>(name);
+          throw_or_mimic<exceptions::no_such_option>(name);
         }
 
         auto opt = iter->second;
 
         //equals provided for long option?
-        if (result[2].length() != 0)
+        if (argu_desc.set_value)
         {
           //parse the option given
 
-          parse_option(opt, name, result[3]);
+          parse_option(opt, name, argu_desc.value);
         }
         else
         {
@@ -2016,8 +2529,13 @@
 
     auto& store = m_parsed[detail->hash()];
 
-    if(value.has_default() && !store.count() && !store.has_default()){
-      parse_default(detail);
+    if (value.has_default()) {
+      if (!store.count() && !store.has_default()) {
+        parse_default(detail);
+      }
+    }
+    else {
+      parse_no_value(detail);
     }
   }
 
@@ -2033,14 +2551,14 @@
 
     //adjust argv for any that couldn't be swallowed
     while (current != argc) {
-      unmatched.push_back(argv[current]);
+      unmatched.emplace_back(argv[current]);
       ++current;
     }
   }
 
   finalise_aliases();
 
-  ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(unmatched));
+  ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(m_defaults), std::move(unmatched));
   return parsed;
 }
 
@@ -2053,7 +2571,9 @@
     auto& detail = *option.second;
     auto hash = detail.hash();
     m_keys[detail.short_name()] = hash;
-    m_keys[detail.long_name()] = hash;
+    for(const auto& long_name : detail.long_names()) {
+      m_keys[long_name] = hash;
+    }
 
     m_parsed.emplace(hash, OptionValue());
   }
@@ -2076,7 +2596,7 @@
 (
   const std::string& group,
   const std::string& s,
-  const std::string& l,
+  const OptionNames& l,
   std::string desc,
   const std::shared_ptr<const Value>& value,
   std::string arg_help
@@ -2090,16 +2610,10 @@
     add_one_option(s, option);
   }
 
-  if (!l.empty())
-  {
-    add_one_option(l, option);
+  for(const auto& long_name : l) {
+    add_one_option(long_name, option);
   }
 
-  m_option_list.push_front(*option.get());
-  auto iter = m_option_list.begin();
-  m_option_map[s] = iter;
-  m_option_map[l] = iter;
-
   //add the help details
   auto& options = m_help[group];
 
@@ -2123,7 +2637,7 @@
 
   if (!in.second)
   {
-    throw_or_mimic<option_exists_error>(option);
+    throw_or_mimic<exceptions::option_already_exists>(option);
   }
 }
 
@@ -2141,7 +2655,7 @@
 
   OptionHelp format;
 
-  size_t longest = 0;
+  std::size_t longest = 0;
 
   String result;
 
@@ -2152,7 +2666,8 @@
 
   for (const auto& o : group->second.options)
   {
-    if (m_positional_set.find(o.l) != m_positional_set.end() &&
+    if (o.l.size() &&
+        m_positional_set.find(o.l.front()) != m_positional_set.end() &&
         !m_show_positional)
     {
       continue;
@@ -2162,22 +2677,26 @@
     longest = (std::max)(longest, stringLength(s));
     format.push_back(std::make_pair(s, String()));
   }
+  longest = (std::min)(longest, OPTION_LONGEST);
 
-  longest = (std::min)(longest, static_cast<size_t>(OPTION_LONGEST));
-
-  //widest allowed description
-  auto allowed = size_t{76} - longest - OPTION_DESC_GAP;
+  //widest allowed description -- min 10 chars for helptext/line
+  std::size_t allowed = 10;
+  if (m_width > allowed + longest + OPTION_DESC_GAP)
+  {
+    allowed = m_width - longest - OPTION_DESC_GAP;
+  }
 
   auto fiter = format.begin();
   for (const auto& o : group->second.options)
   {
-    if (m_positional_set.find(o.l) != m_positional_set.end() &&
+    if (o.l.size() &&
+        m_positional_set.find(o.l.front()) != m_positional_set.end() &&
         !m_show_positional)
     {
       continue;
     }
 
-    auto d = format_description(o, longest + OPTION_DESC_GAP, allowed);
+    auto d = format_description(o, longest + OPTION_DESC_GAP, allowed, m_tab_expansion);
 
     result += fiter->first;
     if (stringLength(fiter->first) > longest)
@@ -2208,7 +2727,7 @@
   const std::vector<std::string>& print_groups
 ) const
 {
-  for (size_t i = 0; i != print_groups.size(); ++i)
+  for (std::size_t i = 0; i != print_groups.size(); ++i)
   {
     const String& group_help_text = help_one_group(print_groups[i]);
     if (empty(group_help_text))
@@ -2228,22 +2747,34 @@
 Options::generate_all_groups_help(String& result) const
 {
   std::vector<std::string> all_groups;
-  all_groups.reserve(m_help.size());
 
-  for (const auto& group : m_help)
-  {
-    all_groups.push_back(group.first);
-  }
+  std::transform(
+    m_help.begin(),
+    m_help.end(),
+    std::back_inserter(all_groups),
+    [] (const std::map<std::string, HelpGroupDetails>::value_type& group)
+    {
+      return group.first;
+    }
+  );
 
   generate_group_help(result, all_groups);
 }
 
 inline
 std::string
-Options::help(const std::vector<std::string>& help_groups) const
+Options::help(const std::vector<std::string>& help_groups, bool print_usage) const
 {
-  String result = m_help_string + "\nUsage:\n  " +
-    toLocalString(m_program) + " " + toLocalString(m_custom_help);
+  String result = m_help_string;
+  if(print_usage)
+  {
+    result+= "\nUsage:\n  " + toLocalString(m_program);
+  }
+
+  if (!m_custom_help.empty())
+  {
+    result += " " + toLocalString(m_custom_help);
+  }
 
   if (!m_positional.empty() && !m_positional_help.empty()) {
     result += " " + toLocalString(m_positional_help);