New Upstream Release - cli11

Ready changes

Summary

Merged new upstream version: 2.3.2+ds (was: 2.1.2+ds).

Resulting package

Built on 2023-04-05T11:17 (took 25m38s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases libcli11-devapt install -t fresh-releases libcli11-doc

Lintian Result

Diff

diff --git a/.clang-format b/.clang-format
index 0879ffa..6a5d106 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,5 +1,5 @@
-Language:        Cpp
-BasedOnStyle:  LLVM
+Language: Cpp
+BasedOnStyle: LLVM
 # AccessModifierOffset: -2
 # AlignAfterOpenBracket: Align
 # AlignConsecutiveAssignments: false
@@ -37,7 +37,7 @@ BinPackParameters: false
 # BreakConstructorInitializersBeforeComma: false
 # BreakAfterJavaFieldAnnotations: false
 # BreakStringLiterals: true
-ColumnLimit:     120
+ColumnLimit: 120
 # CommentPragmas:  '^ IWYU pragma:'
 # ConstructorInitializerAllOnOneLineOrOnePerLine: false
 # ConstructorInitializerIndentWidth: 4
@@ -49,7 +49,7 @@ ColumnLimit:     120
 # ForEachMacros:   [ foreach, Q_FOREACH, BOOST_FOREACH ]
 # IncludeIsMainRegex: '$'
 # IndentCaseLabels: false
-IndentWidth:     4
+IndentWidth: 4
 # IndentWrappedFunctionNames: false
 # JavaScriptQuotes: Leave
 # JavaScriptWrapImports: true
@@ -69,7 +69,7 @@ IndentWidth:     4
 # PenaltyReturnTypeOnItsOwnLine: 60
 # PointerAlignment: Right
 # ReflowComments:  true
-SortIncludes:    true
+SortIncludes: true
 # SpaceAfterCStyleCast: false
 # SpaceAfterTemplateKeyword: true
 # SpaceBeforeAssignmentOperators: true
@@ -81,6 +81,6 @@ SpacesBeforeTrailingComments: 2
 # SpacesInCStyleCastParentheses: false
 # SpacesInParentheses: false
 # SpacesInSquareBrackets: false
-Standard:        Cpp11
-TabWidth:        4
-UseTab:          Never
+Standard: Cpp11
+TabWidth: 4
+UseTab: Never
diff --git a/.clang-tidy b/.clang-tidy
index 075f35d..82450d1 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -1,24 +1,80 @@
 # Checks that will be implemented in future PRs:
 # performance-unnecessary-value-param, hints to ~110 issues. Be careful with implementing the suggested changes of this one, as auto-fixes may break the code
+# bugprone-forwarding-reference-overload probably should be enabled and fixed.
+# clang-diagnostic-float-equal can be fixed by using _a from Catch::literals
+# bugprone-exception-escape due to main being a bit simple in examples
+# modernize-avoid-c-arrays trips up in TEMPLATE_TEST_CASE catch macro
+# modernize-return-braced-init-list triggers on lambdas ?
+# modernize-make-unique requires C++14
+# readability-avoid-const-params-in-decls Affected by the pre-compile split
 
-FormatStyle: file
-
-Checks: '
--*,
-google-*,
--google-runtime-references,
-llvm-include-order,
-llvm-namespace-comment,
-misc-throw-by-value-catch-by-reference,
-modernize*,
--modernize-use-trailing-return-type,
-readability-container-size-empty,
-'
+Checks: |
+  *bugprone*,
+  -bugprone-easily-swappable-parameters,
+  -bugprone-forwarding-reference-overload,
+  -bugprone-exception-escape,
+  clang-analyzer-optin.cplusplus.VirtualCall,
+  clang-analyzer-optin.performance.Padding,
+  -clang-diagnostic-float-equal,
+  cppcoreguidelines-init-variables,
+  cppcoreguidelines-prefer-member-initializer,
+  cppcoreguidelines-pro-type-static-cast-downcast,
+  cppcoreguidelines-slicing,
+  google-*,
+  -google-runtime-references,
+  llvm-include-order,
+  llvm-namespace-comment,
+  misc-definitions-in-headers,
+  misc-misplaced-const,
+  misc-non-copyable-objects,
+  misc-static-assert,
+  misc-throw-by-value-catch-by-reference,
+  misc-throw-by-value-catch-by-reference,
+  misc-uniqueptr-reset-release,
+  misc-unused-parameters,
+  modernize*,
+  -modernize-use-trailing-return-type,
+  -modernize-concat-nested-namespaces,
+  -modernize-return-braced-init-list,
+  -modernize-make-unique,
+  *performance*,
+  -performance-unnecessary-value-param,
+  -performance-inefficient-string-concatenation,
+  readability-const-return-type,
+  readability-container-size-empty,
+  readability-delete-null-pointer,
+  readability-else-after-return,
+  readability-implicit-bool-conversion,
+  readability-inconsistent-declaration-parameter-name,
+  readability-make-member-function-const,
+  readability-misplaced-array-index,
+  readability-non-const-parameter,
+  readability-qualified-auto,
+  readability-redundant-function-ptr-dereference,
+  readability-redundant-smartptr-get,
+  readability-redundant-string-cstr,
+  readability-simplify-subscript-expr,
+  readability-static-accessed-through-instance,
+  readability-static-definition-in-anonymous-namespace,
+  readability-string-compare,
+  readability-suspicious-call-argument,
+  readability-uniqueptr-delete-release,
 
-WarningsAsErrors: '*'
+CheckOptions:
+  - key: google-readability-braces-around-statements.ShortStatementLines
+    value: "3"
+  - key: performance-for-range-copy.WarnOnAllAutoCopies
+    value: true
+  - key: performance-inefficient-string-concatenation.StrictMode
+    value: true
+  - key: performance-unnecessary-value-param.AllowedTypes
+    value: "exception_ptr$;"
+  - key: readability-implicit-bool-conversion.AllowPointerConditions
+    value: true
+  - key: modernize-use-nodiscard.ReplacementString
+    value: "CLI11_NODISCARD"
 
-HeaderFilterRegex: '.*hpp'
+HeaderFilterRegex: "CLI.*hpp"
 
-CheckOptions:
-- key:             google-readability-braces-around-statements.ShortStatementLines
-  value:           '3'
+FormatStyle: file
+# WarningsAsErrors: "*"
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8413542..996d01d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -2,62 +2,91 @@ ci:
   autoupdate_commit_msg: "chore(deps): pre-commit.ci autoupdate"
   autofix_commit_msg: "style: pre-commit.ci fixes"
 
-
 repos:
-- repo: https://github.com/psf/black
-  rev: 21.9b0
-  hooks:
-  - id: black
-
-- repo: https://github.com/pre-commit/pre-commit-hooks
-  rev: v4.0.1
-  hooks:
-  - id: check-added-large-files
-  - id: check-case-conflict
-  - id: check-merge-conflict
-  - id: check-symlinks
-  - id: check-yaml
-  - id: end-of-file-fixer
-  - id: mixed-line-ending
-  - id: trailing-whitespace
-
-- repo: https://github.com/pre-commit/mirrors-clang-format
-  rev: v13.0.0
-  hooks:
-   - id: clang-format
-
-- repo: https://github.com/cheshirekow/cmake-format-precommit
-  rev: v0.6.13
-  hooks:
-  - id: cmake-format
-    additional_dependencies: [pyyaml]
-
-- repo: https://github.com/markdownlint/markdownlint
-  rev: v0.11.0
-  hooks:
-  - id: markdownlint
-    args: ["--style=scripts/mdlint_style.rb"]
-
-- repo: local
-  hooks:
-  - id: remarklint
-    name: remarklint
-    language: node
-    entry: remark
-    types: [markdown]
-    args: ["--frail", "--quiet"]
-    additional_dependencies: [remark, remark-lint, remark-cli, remark-preset-lint-recommended, remark-lint-list-item-indent, remark-lint-no-undefined-references]
-
-- repo: local
-  hooks:
-  - id: disallow-caps
-    name: Disallow improper capitalization
-    language: pygrep
-    entry: PyBind|Numpy|Cmake|CCache|PyTest|Github
-    exclude: .pre-commit-config.yaml
-
-- repo: https://github.com/codespell-project/codespell
-  rev: v2.1.0
-  hooks:
-  - id: codespell
-    args: ["-L", "atleast,ans,doub,inout"]
+  - repo: https://github.com/psf/black
+    rev: 22.12.0
+    hooks:
+      - id: black
+
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.4.0
+    hooks:
+      - id: check-added-large-files
+      - id: check-case-conflict
+      - id: check-merge-conflict
+      - id: check-symlinks
+      - id: check-yaml
+      - id: check-toml
+      - id: end-of-file-fixer
+      - id: mixed-line-ending
+      - id: trailing-whitespace
+      - id: check-shebang-scripts-are-executable
+      - id: check-executables-have-shebangs
+      - id: debug-statements
+
+  - repo: https://github.com/pre-commit/mirrors-clang-format
+    rev: v15.0.6
+    hooks:
+      - id: clang-format
+        types_or: [c++, c, cuda]
+
+  - repo: https://github.com/cheshirekow/cmake-format-precommit
+    rev: v0.6.13
+    hooks:
+      - id: cmake-format
+        additional_dependencies: [pyyaml]
+
+  - repo: https://github.com/pre-commit/mirrors-prettier
+    rev: "v3.0.0-alpha.4"
+    hooks:
+      - id: prettier
+        types_or: [yaml, markdown, html, css, scss, javascript, json]
+        args: [--prose-wrap=always]
+
+  - repo: https://github.com/markdownlint/markdownlint
+    rev: v0.12.0
+    hooks:
+      - id: markdownlint
+        args: ["--style=scripts/mdlint_style.rb"]
+        # Uncomment on macOS - Apple has deprecated Ruby, so macOS is stuck on 2.6
+        # language_version: 3.1.2
+
+  # - repo: local
+  #   hooks:
+  #     - id: remarklint
+  #       name: remarklint
+  #       language: node
+  #       entry: remark
+  #       types: [markdown]
+  #       args: ["--frail", "--quiet"]
+  #       additional_dependencies:
+  #         [
+  #           remark,
+  #           remark-lint,
+  #           remark-cli,
+  #           remark-preset-lint-recommended,
+  #           remark-lint-list-item-indent,
+  #           remark-lint-no-undefined-references,
+  #         ]
+
+  - repo: local
+    hooks:
+      - id: disallow-caps
+        name: Disallow improper capitalization
+        language: pygrep
+        entry: PyBind|Numpy|Cmake|CCache|PyTest|Github
+        exclude: .pre-commit-config.yaml
+
+  - repo: local
+    hooks:
+      - id: avoid-msvc-macro
+        name: Avoid MSVC <=2017 min/max macro (use extra parens)
+        language: pygrep
+        entry: \b(min|max)\(
+        exclude: .pre-commit-config.yaml
+
+  - repo: https://github.com/codespell-project/codespell
+    rev: v2.2.2
+    hooks:
+      - id: codespell
+        args: ["-L", "atleast,ans,doub,inout"]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e35ca38..4fc3ccd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,20 +1,166 @@
 # Changelog
 
+## Version 2.3: Precompilation Support
+
+This version adds a pre-compiled mode to CLI11, which allows you to precompile
+the library, saving time on incremental rebuilds, making CLI11 more competitive
+on compile time with classic compiled CLI libraries. The header-only mode is
+still default, and is not yet distributed via binaries.
+
+- Add `CLI11_PRECOMPILED` as an option. [#762][]
+- Bugfix: Include `<functional>` in `FormatterFwd` [#727][]
+- Bugfix: Add missing `Macros.hpp` to `Error.hpp` [#755][]
+- Bugfix: Fix subcommand callback trigger [#733][]
+- Bugfix: Variable rename to avoid warning [#734][]
+- Bugfix: `split_program_name` single file name error [#740][]
+- Bugfix: Better support for min/max overrides on MSVC [#741][]
+- Bugfix: Support MSVC 2022 [#748][]
+- Bugfix: Support negated flag in config file [#775][]
+- Bugfix: Better errors for some confusing config file situations [#781][]
+- Backend: Restore coverage testing (lost with Travis CI) [#747][]
+
+[#727]: https://github.com/CLIUtils/CLI11/pull/727
+[#733]: https://github.com/CLIUtils/CLI11/pull/733
+[#734]: https://github.com/CLIUtils/CLI11/pull/734
+[#740]: https://github.com/CLIUtils/CLI11/pull/740
+[#741]: https://github.com/CLIUtils/CLI11/pull/741
+[#747]: https://github.com/CLIUtils/CLI11/pull/747
+[#748]: https://github.com/CLIUtils/CLI11/pull/748
+[#755]: https://github.com/CLIUtils/CLI11/pull/755
+[#762]: https://github.com/CLIUtils/CLI11/pull/762
+[#775]: https://github.com/CLIUtils/CLI11/pull/775
+[#781]: https://github.com/CLIUtils/CLI11/pull/781
+
+### Version 2.3.1: Missing implementation
+
+A function implementation was missing after the pre-compile move, missed due to
+the fact we lost 100% after losing coverage checking. We are working on filling
+out 100% coverage again to ensure this doesn't happen again!
+
+- Bugfix: `App::get_option_group` implementation missing [#793][]
+- Bugfix: Fix spacing when setting an empty footer [#796][]
+- Bugfix: Address Klocwork static analysis checking issues [#785][]
+
+[#785]: https://github.com/CLIUtils/CLI11/pull/785
+[#793]: https://github.com/CLIUtils/CLI11/pull/793
+[#796]: https://github.com/CLIUtils/CLI11/pull/796
+
+### Version 2.3.2: Minor maintenance
+
+This version provides a few fixes collected over the last three months before
+adding features for 2.4.
+
+- Bugfix: Consistently use ADL for `lexical_cast`, making it easier to extend
+  for custom template types [#820][]
+- Bugfix: Tweak the parsing of files for flags with `disable_flag_override`
+  [#800][]
+- Bugfix: Handle out of bounds long long [#807][]
+- Bugfix: Spacing of `make_description` min option output [#808][]
+- Bugfix: Print last parsed subcommand's help message [#822][]
+- Bugfix: Avoid floating point warning in GCC 12 [#803][]
+- Bugfix: Fix a few gcc warnings [#813][]
+- Backend: Max CMake tested 3.22 -> 3.24 [#823][]
+
+[#800]: https://github.com/CLIUtils/CLI11/pull/800
+[#803]: https://github.com/CLIUtils/CLI11/pull/803
+[#807]: https://github.com/CLIUtils/CLI11/pull/807
+[#808]: https://github.com/CLIUtils/CLI11/pull/808
+[#813]: https://github.com/CLIUtils/CLI11/pull/813
+[#820]: https://github.com/CLIUtils/CLI11/pull/820
+[#822]: https://github.com/CLIUtils/CLI11/pull/822
+[#823]: https://github.com/CLIUtils/CLI11/pull/823
+
+## Version 2.2: Option and Configuration Flexibility
+
+New features include support for output of an empty vector, a summing option
+policy that can be applied more broadly, and an option to validate optional
+arguments to discriminate from positional arguments. A new validator to check
+for files on a default path is included to allow one or more default paths for
+configuration files or other file arguments. A number of bug fixes and code
+cleanup for various build configurations. Clean up of some error outputs and
+extension of existing capability to new types or situations.
+
+There is a possible minor breaking change in behavior of certain types which
+wrapped an integer, such as `std::atomic<int>` or `std::optional<int>` when used
+in a flag. The default behavior is now as a single argument value vs. summing
+all the arguments. The default summing behavior is now restricted to pure
+integral types, int64_t, int, uint32_t, etc. Use the new `sum` multi option
+policy to revert to the older behavior. The summing behavior on wrapper types
+was not originally intended.
+
+- Add `MultiOptionPolicy::Sum` and refactor the `add_flag` to fix a bug when
+  using `std::optional<bool>` as type. [#709][]
+- Add support for an empty vector result in TOML and as a default string.
+  [#660][]
+- Add `.validate_optional_arguments()` to support discriminating positional
+  arguments from vector option arguments. [#668][]
+- Add `CLI::FileOnDefaultPath` to check for files on a specified default path.
+  [#698][]
+- Change default value display in help messages from `=XXXX` to `[XXXXX]` to
+  make it clearer. [#666][]
+- Modify the Range Validator to support additional types and clean up the error
+  output. [#690][]
+- Bugfix: The trigger on parse modifier did not work on positional argument.s
+  [#713][]
+- Bugfix: The single header file generation was missing custom namespace
+  generation. [#707][]
+- Bugfix: Clean up File Error handling in the argument processing. [#678][]
+- Bugfix: Fix a stack overflow error if nameless commands had fallthrough.
+  [#665][]
+- Bugfix: A subcommand callback could be executed multiple times if it was a
+  member of an option group. [#666][]
+- Bugfix: Fix an issue with vectors of multi argument types where partial
+  argument sets did not result in an error. [#661][]
+- Bugfix: Fix an issue with type the template matching on C++20 and add some CI
+  builds for C++20. [#663][]
+- Bugfix: Fix typo in C++20 detection on MSVC. [#706][]
+- Bugfix: An issue where the detection of RTTI being disabled on certain MSVC
+  platforms did not disable the use of dynamic cast calls. [#666][]
+- Bugfix: Resolve strict-overflow warning on some GCC compilers. [#666][]
+- Backend: Add additional tests concerning the use of aliases for option groups
+  in config files. [#666][]
+- Build: Add support for testing in meson and cleanup symbolic link generation.
+  [#701][], [#697][]
+- Build: Support building in WebAssembly. [#679][]
+
+[#660]: https://github.com/CLIUtils/CLI11/pull/660
+[#661]: https://github.com/CLIUtils/CLI11/pull/661
+[#663]: https://github.com/CLIUtils/CLI11/pull/663
+[#665]: https://github.com/CLIUtils/CLI11/pull/665
+[#666]: https://github.com/CLIUtils/CLI11/pull/666
+[#668]: https://github.com/CLIUtils/CLI11/pull/668
+[#678]: https://github.com/CLIUtils/CLI11/pull/678
+[#679]: https://github.com/CLIUtils/CLI11/pull/679
+[#690]: https://github.com/CLIUtils/CLI11/pull/690
+[#697]: https://github.com/CLIUtils/CLI11/pull/697
+[#698]: https://github.com/CLIUtils/CLI11/pull/698
+[#701]: https://github.com/CLIUtils/CLI11/pull/701
+[#706]: https://github.com/CLIUtils/CLI11/pull/706
+[#707]: https://github.com/CLIUtils/CLI11/pull/707
+[#709]: https://github.com/CLIUtils/CLI11/pull/709
+[#713]: https://github.com/CLIUtils/CLI11/pull/713
+
 ## Version 2.1: Names and callbacks
 
 The name restrictions for options and subcommands are now much looser, allowing
 a wider variety of characters than before, even spaces can be used (use quotes
 to include a space in most shells). The default configuration parser was
 improved, allowing your configuration to sit in a larger file. And option
-callbacks have a few new settings, allowing them to be run even if the option
-is not passed, or every time the option is parsed.
-
-* Option/subcommand name restrictions have been relaxed. Most characters are now allowed. [#627][]
-* The config parser can accept streams, specify a specific section, and inline comment characters are supported [#630][]
-* `force_callback` & `trigger_on_parse` added, allowing a callback to always run on parse even if not present or every time the option is parsed [#631][]
-* Bugfix(cmake): Only add `CONFIGURE_DEPENDS` if CLI11 is the main project [#633][]
-* Bugfix(cmake): Ensure the cmake/pkg-config files install to a arch independent path [#635][]
-* Bugfix: The single header file generation was missing the include guard. [#620][]
+callbacks have a few new settings, allowing them to be run even if the option is
+not passed, or every time the option is parsed.
+
+- Option/subcommand name restrictions have been relaxed. Most characters are now
+  allowed. [#627][]
+- The config parser can accept streams, specify a specific section, and inline
+  comment characters are supported [#630][]
+- `force_callback` & `trigger_on_parse` added, allowing a callback to always run
+  on parse even if not present or every time the option is parsed [#631][]
+- Bugfix(cmake): Only add `CONFIGURE_DEPENDS` if CLI11 is the main project
+  [#633][]
+- Bugfix(cmake): Ensure the cmake/pkg-config files install to a arch independent
+  path [#635][]
+- Bugfix: The single header file generation was missing the include guard.
+  [#620][]
 
 [#620]: https://github.com/CLIUtils/CLI11/pull/620
 [#627]: https://github.com/CLIUtils/CLI11/pull/627
@@ -25,9 +171,10 @@ is not passed, or every time the option is parsed.
 
 ### Version 2.1.1: Quick Windows fix
 
-* A collision with `min`/`max` macros on Windows has been fixed. [#642][]
-* Tests pass with Boost again [#646][]
-* Running the pre-commit hooks in development no longer requires docker for clang-format [#647][]
+- A collision with `min`/`max` macros on Windows has been fixed. [#642][]
+- Tests pass with Boost again [#646][]
+- Running the pre-commit hooks in development no longer requires docker for
+  clang-format [#647][]
 
 [#642]: https://github.com/CLIUtils/CLI11/pull/642
 [#646]: https://github.com/CLIUtils/CLI11/pull/646
@@ -35,11 +182,11 @@ is not passed, or every time the option is parsed.
 
 ## Version 2.1.2: Better subproject builds
 
-* Use `main` for the main branch of the repository [#657][]
-* Bugfix(cmake): Enforce at least C++11 when using CMake target [#656][]
-* Build: Don't run doxygen and CTest includes if a submodule [#656][]
-* Build: Avoid a warning on CMake 3.22 [#656][]
-* Build: Support compiling the tests with an external copy of Catch2 [#653][]
+- Use `main` for the main branch of the repository [#657][]
+- Bugfix(cmake): Enforce at least C++11 when using CMake target [#656][]
+- Build: Don't run doxygen and CTest includes if a submodule [#656][]
+- Build: Avoid a warning on CMake 3.22 [#656][]
+- Build: Support compiling the tests with an external copy of Catch2 [#653][]
 
 [#653]: https://github.com/CLIUtils/CLI11/pull/653
 [#656]: https://github.com/CLIUtils/CLI11/pull/656
@@ -49,40 +196,43 @@ is not passed, or every time the option is parsed.
 
 This version focuses on cleaning up deprecated functionality, and some minor
 default changes. The config processing is TOML compliant now. Atomics and
-complex numbers are directly supported, along with other container
-improvements. A new version flag option has finally been added. Subcommands are
-significantly improved with new features and bugfixes for corner cases.  This
-release contains a lot of backend cleanup, including a complete overhaul of the
-testing system and single file generation system.
-
-* Built-in config format is TOML compliant now [#435][]
-  * Support multiline TOML [#528][]
-  * Support for configurable quotes [#599][]
-  * Support short/positional options in config mode [#443][]
-* More powerful containers, support for `%%` separator [#423][]
-* Support atomic types [#520][] and complex types natively [#423][]
-* Add a type validator `CLI::TypeValidator<TYPE>` [#526][]
-* Add a version flag easily [#452][], with help message [#601][]
-* Support `->silent()` on subcommands. [#529][]
-* Add alias section to help for subcommands [#545][]
-* Allow quotes to specify a program name [#605][]
-* Backend: redesigned MakeSingleFiles to have a higher level of manual control, to support future features. [#546][]
-* Backend: moved testing from GTest to Catch2 [#574][]
-* Bugfix: avoid duplicated and missed calls to the final callback [#584][]
-* Bugfix: support embedded newlines in more places [#592][]
-* Bugfix: avoid listing helpall as a required flag [#530][]
-* Bugfix: avoid a clash with WINDOWS define [#563][]
-* Bugfix: the help flag didn't get processed when a config file was required [#606][]
-* Bugfix: fix description of non-configurable subcommands in config [#604][]
-* Build: support pkg-config [#523][]
+complex numbers are directly supported, along with other container improvements.
+A new version flag option has finally been added. Subcommands are significantly
+improved with new features and bugfixes for corner cases. This release contains
+a lot of backend cleanup, including a complete overhaul of the testing system
+and single file generation system.
+
+- Built-in config format is TOML compliant now [#435][]
+  - Support multiline TOML [#528][]
+  - Support for configurable quotes [#599][]
+  - Support short/positional options in config mode [#443][]
+- More powerful containers, support for `%%` separator [#423][]
+- Support atomic types [#520][] and complex types natively [#423][]
+- Add a type validator `CLI::TypeValidator<TYPE>` [#526][]
+- Add a version flag easily [#452][], with help message [#601][]
+- Support `->silent()` on subcommands. [#529][]
+- Add alias section to help for subcommands [#545][]
+- Allow quotes to specify a program name [#605][]
+- Backend: redesigned MakeSingleFiles to have a higher level of manual control,
+  to support future features. [#546][]
+- Backend: moved testing from GTest to Catch2 [#574][]
+- Bugfix: avoid duplicated and missed calls to the final callback [#584][]
+- Bugfix: support embedded newlines in more places [#592][]
+- Bugfix: avoid listing helpall as a required flag [#530][]
+- Bugfix: avoid a clash with WINDOWS define [#563][]
+- Bugfix: the help flag didn't get processed when a config file was required
+  [#606][]
+- Bugfix: fix description of non-configurable subcommands in config [#604][]
+- Build: support pkg-config [#523][]
 
 > ### Converting from CLI11 1.9
 >
-> * Removed deprecated set commands, use validators instead. [#565][]
-> * The final "defaulted" bool has been removed, use `->capture_default_str()`
->   instead. Use `app.option_defaults()->always_capture_default()` to set this for
->   all future options. [#597][]
-> * Use `add_option` on a complex number instead of `add_complex`, which has been removed.
+> - Removed deprecated set commands, use validators instead. [#565][]
+> - The final "defaulted" bool has been removed, use `->capture_default_str()`
+>   instead. Use `app.option_defaults()->always_capture_default()` to set this
+>   for all future options. [#597][]
+> - Use `add_option` on a complex number instead of `add_complex`, which has
+>   been removed.
 
 [#423]: https://github.com/CLIUtils/CLI11/pull/423
 [#435]: https://github.com/CLIUtils/CLI11/pull/435
@@ -110,48 +260,61 @@ testing system and single file generation system.
 
 ## Version 1.9: Config files and cleanup
 
-Config file handling was revamped to fix common issues, and now supports reading [TOML](https://github.com/toml-lang/toml).
+Config file handling was revamped to fix common issues, and now supports reading
+[TOML](https://github.com/toml-lang/toml).
 
 Adding options is significantly more powerful with support for things like
 `std::tuple` and `std::array`, including with transforms. Several new
-configuration options were added to facilitate a wider variety of apps.  GCC
-4.7 is no longer supported.
-
-* Config files refactored, supports TOML (may become default output in 2.0) [#362][]
-* Added two template parameter form of `add_option`, allowing `std::optional` to be supported without a special import [#285][]
-* `string_view` now supported in reasonable places [#300][], [#285][]
-* `immediate_callback`, `final_callback`, and `parse_complete_callback` added to support controlling the App callback order [#292][], [#313][]
-* Multiple positional arguments maintain order if `positionals_at_end` is set. [#306][]
-* Pair/tuple/array now supported, and validators indexed to specific components in the objects [#307][], [#310][]
-* Footer callbacks supported [#309][]
-* Subcommands now support needs (including nameless subcommands) [#317][]
-* More flexible type size, more useful `add_complex` [#325][], [#370][]
-* Added new validators `CLI::NonNegativeNumber` and `CLI::PositiveNumber` [#342][]
-* Transform now supports arrays [#349][]
-* Option groups can be hidden [#356][]
-* Add `CLI::deprecate_option` and `CLI::retire_option` functions [#358][]
-* More flexible and safer Option `default_val` [#387][]
-* Backend: Cleaner type traits [#286][]
-* Backend: File checking updates [#341][]
-* Backend: Using pre-commit to format, checked in GitHub Actions [#336][]
-* Backend: Clang-tidy checked again, CMake option now `CL11_CLANG_TIDY` [#390][]
-* Backend: Warning cleanup, more checks from klocwork [#350][], Effective C++ [#354][], clang-tidy [#360][], CUDA NVCC [#365][], cross compile [#373][], sign conversion [#382][], and cpplint [#400][]
-* Docs: CLI11 Tutorial now hosted in the same repository [#304][], [#318][], [#374][]
-* Bugfix: Fixed undefined behavior in `checked_multiply` [#290][]
-* Bugfix: `->check()` was adding the name to the wrong validator [#320][]
-* Bugfix: Resetting config option works properly [#301][]
-* Bugfix: Hidden flags were showing up in error printout [#333][]
-* Bugfix: Enum conversion no longer broken if stream operator added [#348][]
-* Build: The meson build system supported [#299][]
-* Build: GCC 4.7 is no longer supported, due mostly to GoogleTest. GCC 4.8+ is now required. [#160][]
-* Build: Restructured significant portions of CMake build system [#394][]
+configuration options were added to facilitate a wider variety of apps. GCC 4.7
+is no longer supported.
+
+- Config files refactored, supports TOML (may become default output in 2.0)
+  [#362][]
+- Added two template parameter form of `add_option`, allowing `std::optional` to
+  be supported without a special import [#285][]
+- `string_view` now supported in reasonable places [#300][], [#285][]
+- `immediate_callback`, `final_callback`, and `parse_complete_callback` added to
+  support controlling the App callback order [#292][], [#313][]
+- Multiple positional arguments maintain order if `positionals_at_end` is set.
+  [#306][]
+- Pair/tuple/array now supported, and validators indexed to specific components
+  in the objects [#307][], [#310][]
+- Footer callbacks supported [#309][]
+- Subcommands now support needs (including nameless subcommands) [#317][]
+- More flexible type size, more useful `add_complex` [#325][], [#370][]
+- Added new validators `CLI::NonNegativeNumber` and `CLI::PositiveNumber`
+  [#342][]
+- Transform now supports arrays [#349][]
+- Option groups can be hidden [#356][]
+- Add `CLI::deprecate_option` and `CLI::retire_option` functions [#358][]
+- More flexible and safer Option `default_val` [#387][]
+- Backend: Cleaner type traits [#286][]
+- Backend: File checking updates [#341][]
+- Backend: Using pre-commit to format, checked in GitHub Actions [#336][]
+- Backend: Clang-tidy checked again, CMake option now `CL11_CLANG_TIDY` [#390][]
+- Backend: Warning cleanup, more checks from klocwork [#350][], Effective C++
+  [#354][], clang-tidy [#360][], CUDA NVCC [#365][], cross compile [#373][],
+  sign conversion [#382][], and cpplint [#400][]
+- Docs: CLI11 Tutorial now hosted in the same repository [#304][], [#318][],
+  [#374][]
+- Bugfix: Fixed undefined behavior in `checked_multiply` [#290][]
+- Bugfix: `->check()` was adding the name to the wrong validator [#320][]
+- Bugfix: Resetting config option works properly [#301][]
+- Bugfix: Hidden flags were showing up in error printout [#333][]
+- Bugfix: Enum conversion no longer broken if stream operator added [#348][]
+- Build: The meson build system supported [#299][]
+- Build: GCC 4.7 is no longer supported, due mostly to GoogleTest. GCC 4.8+ is
+  now required. [#160][]
+- Build: Restructured significant portions of CMake build system [#394][]
 
 > ### Converting from CLI11 1.8
 >
-> * Some deprecated methods dropped
->   * `add_set*` should be replaced with `->check`/`->transform` and `CLI::IsMember` since 1.8
->   * `get_defaultval` was replaced by `get_default_str`  in 1.8
-> * The true/false 4th argument to `add_option` is expected to be removed in 2.0, use `->capture_default_str()` since 1.8
+> - Some deprecated methods dropped
+>   - `add_set*` should be replaced with `->check`/`->transform` and
+>     `CLI::IsMember` since 1.8
+>   - `get_defaultval` was replaced by `get_default_str` in 1.8
+> - The true/false 4th argument to `add_option` is expected to be removed in
+>   2.0, use `->capture_default_str()` since 1.8
 
 [#160]: https://github.com/CLIUtils/CLI11/pull/160
 [#285]: https://github.com/CLIUtils/CLI11/pull/285
@@ -197,13 +360,13 @@ configuration options were added to facilitate a wider variety of apps.  GCC
 
 This is a patch version that backports fixes from the development of 2.0.
 
-* Support relative inclusion [#475][]
-* Fix cases where spaces in paths could break CMake support [#471][]
-* Fix an issue with string conversion [#421][]
-* Cross-compiling improvement for Conan.io [#430][]
-* Fix option group default propagation [#450][]
-* Fix for C++20 [#459][]
-* Support compiling with RTTI off [#461][]
+- Support relative inclusion [#475][]
+- Fix cases where spaces in paths could break CMake support [#471][]
+- Fix an issue with string conversion [#421][]
+- Cross-compiling improvement for Conan.io [#430][]
+- Fix option group default propagation [#450][]
+- Fix for C++20 [#459][]
+- Support compiling with RTTI off [#461][]
 
 [#421]: https://github.com/CLIUtils/CLI11/pull/421
 [#430]: https://github.com/CLIUtils/CLI11/pull/430
@@ -215,41 +378,73 @@ This is a patch version that backports fixes from the development of 2.0.
 
 ## Version 1.8: Transformers, default strings, and flags
 
-Set handling has been completely replaced by a new backend that works as a Validator or Transformer. This provides a single interface instead of the 16 different functions in App. It also allows ordered collections to be used, custom functions for filtering, and better help and error messages. You can also use a collection of pairs (like `std::map`) to transform the match into an output. Also new are inverted flags, which can cancel or reduce the count of flags, and can also support general flag types. A new `add_option_fn` lets you more easily program CLI11 options with the types you choose. Vector options now support a custom separator. Apps can now be composed with unnamed subcommand support. The final bool "defaults" flag when creating options has been replaced by `->capture_default_str()` (ending an old limitation in construction made this possible); the old method is still available but may be removed in future versions.
-
-* Replaced default help capture: `.add_option("name", value, "", True)` becomes `.add_option("name", value)->capture_default_str()` [#242][]
-* Added `.always_capture_default()` [#242][]
-* New `CLI::IsMember` validator replaces set validation [#222][]
-* `IsMember` also supports container of pairs, transform allows modification of result [#228][]
-* Added new Transformers, `CLI::AsNumberWithUnit` and `CLI::AsSizeValue` [#253][]
-* Much more powerful flags with different values [#211][], general types [#235][]
-* `add_option` now supports bool due to unified bool handling [#211][]
-* Support for composable unnamed subcommands [#216][]
-* Reparsing is better supported with `.remaining_for_passthrough()` [#265][]
-* Custom vector separator using `->delimiter(char)` [#209][], [#221][], [#240][]
-* Validators added for IP4 addresses and positive numbers [#210][] and numbers [#262][]
-* Minimum required Boost for optional Optionals has been corrected to 1.61 [#226][]
-* Positionals can stop options from being parsed with `app.positionals_at_end()` [#223][]
-* Added `validate_positionals` [#262][]
-* Positional parsing is much more powerful [#251][], duplicates supported [#247][]
-* Validators can be negated with `!` [#230][], and now handle tname functions [#228][]
-* Better enum support and streaming helper [#233][] and [#228][]
-* Cleanup for shadow warnings [#232][]
-* Better alignment on multiline descriptions [#269][]
-* Better support for aarch64 [#266][]
-* Respect `BUILD_TESTING` only if CLI11 is the main project; otherwise, `CLI11_TESTING` must be used [#277][]
-* Drop auto-detection of experimental optional and boost::optional; must be enabled explicitly (too fragile) [#277][] [#279][]
+Set handling has been completely replaced by a new backend that works as a
+Validator or Transformer. This provides a single interface instead of the 16
+different functions in App. It also allows ordered collections to be used,
+custom functions for filtering, and better help and error messages. You can also
+use a collection of pairs (like `std::map`) to transform the match into an
+output. Also new are inverted flags, which can cancel or reduce the count of
+flags, and can also support general flag types. A new `add_option_fn` lets you
+more easily program CLI11 options with the types you choose. Vector options now
+support a custom separator. Apps can now be composed with unnamed subcommand
+support. The final bool "defaults" flag when creating options has been replaced
+by `->capture_default_str()` (ending an old limitation in construction made this
+possible); the old method is still available but may be removed in future
+versions.
+
+- Replaced default help capture: `.add_option("name", value, "", True)` becomes
+  `.add_option("name", value)->capture_default_str()` [#242][]
+- Added `.always_capture_default()` [#242][]
+- New `CLI::IsMember` validator replaces set validation [#222][]
+- `IsMember` also supports container of pairs, transform allows modification of
+  result [#228][]
+- Added new Transformers, `CLI::AsNumberWithUnit` and `CLI::AsSizeValue`
+  [#253][]
+- Much more powerful flags with different values [#211][], general types
+  [#235][]
+- `add_option` now supports bool due to unified bool handling [#211][]
+- Support for composable unnamed subcommands [#216][]
+- Reparsing is better supported with `.remaining_for_passthrough()` [#265][]
+- Custom vector separator using `->delimiter(char)` [#209][], [#221][], [#240][]
+- Validators added for IP4 addresses and positive numbers [#210][] and numbers
+  [#262][]
+- Minimum required Boost for optional Optionals has been corrected to 1.61
+  [#226][]
+- Positionals can stop options from being parsed with `app.positionals_at_end()`
+  [#223][]
+- Added `validate_positionals` [#262][]
+- Positional parsing is much more powerful [#251][], duplicates supported
+  [#247][]
+- Validators can be negated with `!` [#230][], and now handle tname functions
+  [#228][]
+- Better enum support and streaming helper [#233][] and [#228][]
+- Cleanup for shadow warnings [#232][]
+- Better alignment on multiline descriptions [#269][]
+- Better support for aarch64 [#266][]
+- Respect `BUILD_TESTING` only if CLI11 is the main project; otherwise,
+  `CLI11_TESTING` must be used [#277][]
+- Drop auto-detection of experimental optional and boost::optional; must be
+  enabled explicitly (too fragile) [#277][] [#279][]
 
 > ### Converting from CLI11 1.7
 >
-> * `.add_option(..., true)` should be replaced by `.add_option(...)->capture_default_str()` or `app.option_defaults()->always_capture_default()` can be used
-> * `app.add_set("--name", value, {"choice1", "choice2"})` should become `app.add_option("--name", value)->check(CLI::IsMember({"choice1", "choice2"}))`
-> * The `_ignore_case` version of this can be replaced by adding `CLI::ignore_case` to the argument list in `IsMember`
-> * The `_ignore_underscore` version of this can be replaced by adding `CLI::ignore_underscore` to the argument list in `IsMember`
-> * The `_ignore_case_underscore` version of this can be replaced by adding both functions listed above to the argument list in `IsMember`
-> * If you want an exact match to the original choice after one of the modifier functions matches, use `->transform` instead of `->check`
-> * The `_mutable` versions of this can be replaced by passing a pointer or shared pointer into `IsMember`
-> * An error with sets now produces a `ValidationError` instead of a `ConversionError`
+> - `.add_option(..., true)` should be replaced by
+>   `.add_option(...)->capture_default_str()` or
+>   `app.option_defaults()->always_capture_default()` can be used
+> - `app.add_set("--name", value, {"choice1", "choice2"})` should become
+>   `app.add_option("--name", value)->check(CLI::IsMember({"choice1", "choice2"}))`
+> - The `_ignore_case` version of this can be replaced by adding
+>   `CLI::ignore_case` to the argument list in `IsMember`
+> - The `_ignore_underscore` version of this can be replaced by adding
+>   `CLI::ignore_underscore` to the argument list in `IsMember`
+> - The `_ignore_case_underscore` version of this can be replaced by adding both
+>   functions listed above to the argument list in `IsMember`
+> - If you want an exact match to the original choice after one of the modifier
+>   functions matches, use `->transform` instead of `->check`
+> - The `_mutable` versions of this can be replaced by passing a pointer or
+>   shared pointer into `IsMember`
+> - An error with sets now produces a `ValidationError` instead of a
+>   `ConversionError`
 
 [#209]: https://github.com/CLIUtils/CLI11/pull/209
 [#210]: https://github.com/CLIUtils/CLI11/pull/210
@@ -278,29 +473,50 @@ Set handling has been completely replaced by a new backend that works as a Valid
 
 ## Version 1.7: Parse breakup
 
-The parsing procedure now maps much more sensibly to complex, nested subcommand structures. Each phase of the parsing happens on all subcommands before moving on with the next phase of the parse. This allows several features, like required environment variables, to work properly even through subcommand boundaries.
-Passing the same subcommand multiple times is better supported. Several new features were added as well, including Windows style option support, parsing strings directly, and ignoring underscores in names. Adding a set that you plan to change later must now be done with `add_mutable_set`.
-
-* Support Windows style options with `->allow_windows_style_options`. [#187][] On by default on Windows. [#190][]
-* Added `parse(string)` to split up and parse a command-line style string directly. [#186][]
-* Added `ignore_underscore` and related functions, to ignore underscores when matching names. [#185][]
-* The default INI Config will now add quotes to strings with spaces [#195][]
-* The default message now will mention the help-all flag also if present [#197][]
-* Added `->description` to set Option descriptions [#199][]
-* Mutating sets (introduced in Version 1.6) now have a clear add method, `add_mutable_set*`, since the set reference should not expire [#200][]
-* Subcommands now track how many times they were parsed in a parsing process. `count()` with no arguments will return the number of times a subcommand was encountered. [#178][]
-* Parsing is now done in phases: `shortcurcuits`, `ini`, `env`, `callbacks`, and `requirements`; all subcommands complete a phase before moving on. [#178][]
-* Calling parse multiple times is now officially supported without `clear` (automatic). [#178][]
-* Dropped the mostly undocumented `short_circuit` property, as help flag parsing is a bit more complex, and the default callback behavior of options now works properly. [#179][]
-* Use the standard `BUILD_TESTING` over `CLI11_TESTING` if defined [#183][]
-* Cleanup warnings [#191][]
-* Remove deprecated names: `set_footer`, `set_name`, `set_callback`, and `set_type_name`. Use without the `set_` instead. [#192][]
+The parsing procedure now maps much more sensibly to complex, nested subcommand
+structures. Each phase of the parsing happens on all subcommands before moving
+on with the next phase of the parse. This allows several features, like required
+environment variables, to work properly even through subcommand boundaries.
+Passing the same subcommand multiple times is better supported. Several new
+features were added as well, including Windows style option support, parsing
+strings directly, and ignoring underscores in names. Adding a set that you plan
+to change later must now be done with `add_mutable_set`.
+
+- Support Windows style options with `->allow_windows_style_options`. [#187][]
+  On by default on Windows. [#190][]
+- Added `parse(string)` to split up and parse a command-line style string
+  directly. [#186][]
+- Added `ignore_underscore` and related functions, to ignore underscores when
+  matching names. [#185][]
+- The default INI Config will now add quotes to strings with spaces [#195][]
+- The default message now will mention the help-all flag also if present
+  [#197][]
+- Added `->description` to set Option descriptions [#199][]
+- Mutating sets (introduced in Version 1.6) now have a clear add method,
+  `add_mutable_set*`, since the set reference should not expire [#200][]
+- Subcommands now track how many times they were parsed in a parsing process.
+  `count()` with no arguments will return the number of times a subcommand was
+  encountered. [#178][]
+- Parsing is now done in phases: `shortcurcuits`, `ini`, `env`, `callbacks`, and
+  `requirements`; all subcommands complete a phase before moving on. [#178][]
+- Calling parse multiple times is now officially supported without `clear`
+  (automatic). [#178][]
+- Dropped the mostly undocumented `short_circuit` property, as help flag parsing
+  is a bit more complex, and the default callback behavior of options now works
+  properly. [#179][]
+- Use the standard `BUILD_TESTING` over `CLI11_TESTING` if defined [#183][]
+- Cleanup warnings [#191][]
+- Remove deprecated names: `set_footer`, `set_name`, `set_callback`, and
+  `set_type_name`. Use without the `set_` instead. [#192][]
 
 > ### Converting from CLI11 1.6
 >
-> * `->short_circuit()` is no longer needed, just remove it if you were using it - raising an exception will happen in the proper place now without it.
-> * `->add_set*` becomes `->add_mutable_set*` if you were using the editable set feature
-> * `footer`, `name`, `callback`, and `type_name` must be used instead of the `set_*` versions (deprecated previously).
+> - `->short_circuit()` is no longer needed, just remove it if you were using
+>   it - raising an exception will happen in the proper place now without it.
+> - `->add_set*` becomes `->add_mutable_set*` if you were using the editable set
+>   feature
+> - `footer`, `name`, `callback`, and `type_name` must be used instead of the
+>   `set_*` versions (deprecated previously).
 
 [#178]: https://github.com/CLIUtils/CLI11/pull/178
 [#183]: https://github.com/CLIUtils/CLI11/pull/183
@@ -317,11 +533,12 @@ Passing the same subcommand multiple times is better supported. Several new feat
 
 ### Version 1.7.1: Quick patch
 
-This version provides a quick patch for a (correct) warning from GCC 8 for the windows options code.
+This version provides a quick patch for a (correct) warning from GCC 8 for the
+windows options code.
 
-* Fix for Windows style option parsing [#201][]
-* Improve `add_subcommand` when throwing an exception [#204][]
-* Better metadata for Conan package [#202][]
+- Fix for Windows style option parsing [#201][]
+- Improve `add_subcommand` when throwing an exception [#204][]
+- Better metadata for Conan package [#202][]
 
 [#201]: https://github.com/CLIUtils/CLI11/pull/201
 [#202]: https://github.com/CLIUtils/CLI11/pull/202
@@ -329,66 +546,78 @@ This version provides a quick patch for a (correct) warning from GCC 8 for the w
 
 ## Version 1.6: Formatting help
 
-Added a new formatting system [#109][]. You can now set the formatter on Apps. This has also simplified the internals of Apps and Options a bit by separating most formatting code.
+Added a new formatting system [#109][]. You can now set the formatter on Apps.
+This has also simplified the internals of Apps and Options a bit by separating
+most formatting code.
 
-* Added `CLI::Formatter` and `formatter` slot for apps, inherited.
-* `FormatterBase` is the minimum required.
-* `FormatterLambda` provides for the easy addition of an arbitrary function.
-* Added `help_all` support (not added by default).
+- Added `CLI::Formatter` and `formatter` slot for apps, inherited.
+- `FormatterBase` is the minimum required.
+- `FormatterLambda` provides for the easy addition of an arbitrary function.
+- Added `help_all` support (not added by default).
 
 Changes to the help system (most normal users will not notice this):
 
-* Renamed `single_name` to `get_name(false, false)` (the default).
-* The old `get_name()` is now `get_name(false, true)`.
-* The old `get_pname()` is now `get_name(true, false)`.
-* Removed `help_*` functions.
-* Protected function `_has_help_positional` removed.
-* `format_help` can now be chained.
-* Added getters for the missing parts of options (help no longer uses any private parts).
-* Help flags now use new `short_circuit` property to simplify parsing. [#121][]
+- Renamed `single_name` to `get_name(false, false)` (the default).
+- The old `get_name()` is now `get_name(false, true)`.
+- The old `get_pname()` is now `get_name(true, false)`.
+- Removed `help_*` functions.
+- Protected function `_has_help_positional` removed.
+- `format_help` can now be chained.
+- Added getters for the missing parts of options (help no longer uses any
+  private parts).
+- Help flags now use new `short_circuit` property to simplify parsing. [#121][]
 
 New for Config file reading and writing [#121][]:
 
-* Overridable, bidirectional Config.
-* ConfigINI provided and used by default.
-* Renamed ini to config in many places.
-* Has `config_formatter()` and `get_config_formatter()`.
-* Dropped prefix argument from `config_to_str`.
-* Added `ConfigItem`.
-* Added an example of a custom config format using [nlohmann/json][]. [#138][]
+- Overridable, bidirectional Config.
+- ConfigINI provided and used by default.
+- Renamed ini to config in many places.
+- Has `config_formatter()` and `get_config_formatter()`.
+- Dropped prefix argument from `config_to_str`.
+- Added `ConfigItem`.
+- Added an example of a custom config format using [nlohmann/json][]. [#138][]
 
-Validators are now much more powerful [#118][], all built in validators upgraded to the new form:
+Validators are now much more powerful [#118][], all built in validators upgraded
+to the new form:
 
-* A subclass of `CLI::Validator` is now also accepted.
-* They now can set the type name to things like `PATH` and `INT in [1-4]`.
-* Validators can be combined with `&` and `|`.
-* Old form simple validators are still accepted.
+- A subclass of `CLI::Validator` is now also accepted.
+- They now can set the type name to things like `PATH` and `INT in [1-4]`.
+- Validators can be combined with `&` and `|`.
+- Old form simple validators are still accepted.
 
 Other changes:
 
-* Fixing `parse(args)`'s `args` setting and ordering after parse. [#141][]
-* Replaced `set_custom_option` with `type_name` and `type_size` instead of `set_custom_option`. Methods return `this`. [#136][]
-* Dropped `set_` on Option's `type_name`, `default_str`, and `default_val`. [#136][]
-* Removed `set_` from App's `failure_message`, `footer`, `callback`, and `name`. [#136][]
-* Fixed support `N<-1` for `type_size`. [#140][]
-* Added `->each()` to make adding custom callbacks easier. [#126][]
-* Allow empty options `add_option("-n",{})` to be edited later with `each` [#142][]
-* Added filter argument to `get_subcommands`, `get_options`; use empty filter `{}` to avoid filtering.
-* Added `get_groups()` to get groups.
-* Better support for manual options with `get_option`, `set_results`, and `empty`. [#119][]
-* `lname` and `sname` have getters, added `const get_parent`. [#120][]
-* Using `add_set` will now capture L-values for sets, allowing further modification. [#113][]
-* Dropped duplicate way to run `get_type_name` (`get_typeval`).
-* Removed `requires` in favor of `needs` (deprecated in last version). [#112][]
-* Const added to argv. [#126][]
+- Fixing `parse(args)`'s `args` setting and ordering after parse. [#141][]
+- Replaced `set_custom_option` with `type_name` and `type_size` instead of
+  `set_custom_option`. Methods return `this`. [#136][]
+- Dropped `set_` on Option's `type_name`, `default_str`, and `default_val`.
+  [#136][]
+- Removed `set_` from App's `failure_message`, `footer`, `callback`, and `name`.
+  [#136][]
+- Fixed support `N<-1` for `type_size`. [#140][]
+- Added `->each()` to make adding custom callbacks easier. [#126][]
+- Allow empty options `add_option("-n",{})` to be edited later with `each`
+  [#142][]
+- Added filter argument to `get_subcommands`, `get_options`; use empty filter
+  `{}` to avoid filtering.
+- Added `get_groups()` to get groups.
+- Better support for manual options with `get_option`, `set_results`, and
+  `empty`. [#119][]
+- `lname` and `sname` have getters, added `const get_parent`. [#120][]
+- Using `add_set` will now capture L-values for sets, allowing further
+  modification. [#113][]
+- Dropped duplicate way to run `get_type_name` (`get_typeval`).
+- Removed `requires` in favor of `needs` (deprecated in last version). [#112][]
+- Const added to argv. [#126][]
 
 Backend and testing changes:
 
-* Internally, `type_name` is now a lambda function; for sets, this reads the set live. [#116][]
-* Cleaner tests without `app.reset()` (and `reset` is now `clear`). [#141][]
-* Better CMake policy handling. [#110][]
-* Includes are properly sorted. [#120][]
-* Testing (only) now uses submodules. [#111][]
+- Internally, `type_name` is now a lambda function; for sets, this reads the set
+  live. [#116][]
+- Cleaner tests without `app.reset()` (and `reset` is now `clear`). [#141][]
+- Better CMake policy handling. [#110][]
+- Includes are properly sorted. [#120][]
+- Testing (only) now uses submodules. [#111][]
 
 [#109]: https://github.com/CLIUtils/CLI11/pull/109
 [#110]: https://github.com/CLIUtils/CLI11/pull/110
@@ -406,18 +635,19 @@ Backend and testing changes:
 [#140]: https://github.com/CLIUtils/CLI11/pull/140
 [#141]: https://github.com/CLIUtils/CLI11/pull/141
 [#142]: https://github.com/CLIUtils/CLI11/pull/142
-
 [nlohmann/json]: https://github.com/nlohmann/json
 
 ### Version 1.6.1: Platform fixes
 
-This version provides a few fixes for special cases, such as mixing with `Windows.h` and better defaults
-for systems like Hunter. The one new feature is the ability to produce "branded" single file output for
-providing custom namespaces or custom macro names.
+This version provides a few fixes for special cases, such as mixing with
+`Windows.h` and better defaults for systems like Hunter. The one new feature is
+the ability to produce "branded" single file output for providing custom
+namespaces or custom macro names.
 
-* Added fix and test for including Windows.h [#145][]
-* No longer build single file by default if main project, supports systems stuck on Python 2.6 [#149][], [#151][]
-* Branding support for single file output [#150][]
+- Added fix and test for including Windows.h [#145][]
+- No longer build single file by default if main project, supports systems stuck
+  on Python 2.6 [#149][], [#151][]
+- Branding support for single file output [#150][]
 
 [#145]: https://github.com/CLIUtils/CLI11/pull/145
 [#149]: https://github.com/CLIUtils/CLI11/pull/149
@@ -426,19 +656,23 @@ providing custom namespaces or custom macro names.
 
 ### Version 1.6.2: Help-all
 
-This version fixes some formatting bugs with help-all. It also adds fixes for several warnings, including an experimental optional error on Clang 7. Several smaller fixes.
-
-* Fixed help-all formatting [#163][]
-  * Printing help-all on nested command now fixed (App)
-  * Missing space after help-all restored (Default formatter)
-  * More detail printed on help all (Default formatter)
-  * Help-all subcommands get indented with inner blank lines removed (Default formatter)
-  * `detail::find_and_replace` added to utilities
-* Fixed CMake install as subproject with `CLI11_INSTALL` flag. [#156][]
-* Fixed warning about local variable hiding class member with MSVC [#157][]
-* Fixed compile error with default settings on Clang 7 and libc++ [#158][]
-* Fixed special case of `--help` on subcommands (general fix planned for 1.7) [#168][]
-* Removing an option with links  [#179][]
+This version fixes some formatting bugs with help-all. It also adds fixes for
+several warnings, including an experimental optional error on Clang 7. Several
+smaller fixes.
+
+- Fixed help-all formatting [#163][]
+  - Printing help-all on nested command now fixed (App)
+  - Missing space after help-all restored (Default formatter)
+  - More detail printed on help all (Default formatter)
+  - Help-all subcommands get indented with inner blank lines removed (Default
+    formatter)
+  - `detail::find_and_replace` added to utilities
+- Fixed CMake install as subproject with `CLI11_INSTALL` flag. [#156][]
+- Fixed warning about local variable hiding class member with MSVC [#157][]
+- Fixed compile error with default settings on Clang 7 and libc++ [#158][]
+- Fixed special case of `--help` on subcommands (general fix planned for 1.7)
+  [#168][]
+- Removing an option with links [#179][]
 
 [#156]: https://github.com/CLIUtils/CLI11/issues/156
 [#157]: https://github.com/CLIUtils/CLI11/issues/157
@@ -449,28 +683,39 @@ This version fixes some formatting bugs with help-all. It also adds fixes for se
 
 ## Version 1.5: Optionals
 
-This version introduced support for optionals, along with clarification and examples of custom conversion overloads. Enums now have been dropped from the automatic conversion system, allowing explicit protection for out-of-range ints (or a completely custom conversion). This version has some internal cleanup and improved support for the newest compilers. Several bugs were fixed, as well.
+This version introduced support for optionals, along with clarification and
+examples of custom conversion overloads. Enums now have been dropped from the
+automatic conversion system, allowing explicit protection for out-of-range ints
+(or a completely custom conversion). This version has some internal cleanup and
+improved support for the newest compilers. Several bugs were fixed, as well.
 
 Note: This is the final release with `requires`, please switch to `needs`.
 
-* Fix unlimited short options eating two values before checking for positionals when no space present [#90][]
-* Symmetric exclude text when excluding options, exclude can be called multiple times [#64][]
-* Support for `std::optional`, `std::experimental::optional`, and `boost::optional` added if `__has_include` is supported [#95][]
-* All macros/CMake variables now start with `CLI11_` instead of just `CLI_` [#95][]
-* The internal stream was not being cleared before use in some cases. Fixed. [#95][]
-* Using an enum now requires explicit conversion overload [#97][]
-* The separator `--` now is removed when it ends unlimited arguments [#100][]
+- Fix unlimited short options eating two values before checking for positionals
+  when no space present [#90][]
+- Symmetric exclude text when excluding options, exclude can be called multiple
+  times [#64][]
+- Support for `std::optional`, `std::experimental::optional`, and
+  `boost::optional` added if `__has_include` is supported [#95][]
+- All macros/CMake variables now start with `CLI11_` instead of just `CLI_`
+  [#95][]
+- The internal stream was not being cleared before use in some cases. Fixed.
+  [#95][]
+- Using an enum now requires explicit conversion overload [#97][]
+- The separator `--` now is removed when it ends unlimited arguments [#100][]
 
 Other, non-user facing changes:
 
-* Added `Macros.hpp` with better C++ mode discovery [#95][]
-* Deprecated macros added for all platforms
-* C++17 is now tested on supported platforms [#95][]
-* Informational printout now added to CTest [#95][]
-* Better single file generation [#95][]
-* Added support for GTest on MSVC 2017 (but not in C++17 mode, will need next version of GTest)
-* Types now have a specific size, separate from the expected number - cleaner and more powerful internally [#92][]
-* Examples now run as part of testing [#99][]
+- Added `Macros.hpp` with better C++ mode discovery [#95][]
+- Deprecated macros added for all platforms
+- C++17 is now tested on supported platforms [#95][]
+- Informational printout now added to CTest [#95][]
+- Better single file generation [#95][]
+- Added support for GTest on MSVC 2017 (but not in C++17 mode, will need next
+  version of GTest)
+- Types now have a specific size, separate from the expected number - cleaner
+  and more powerful internally [#92][]
+- Examples now run as part of testing [#99][]
 
 [#64]: https://github.com/CLIUtils/CLI11/issues/64
 [#90]: https://github.com/CLIUtils/CLI11/issues/90
@@ -482,15 +727,19 @@ Other, non-user facing changes:
 
 ### Version 1.5.1: Access
 
-This patch release adds better access to the App programmatically, to assist with writing custom converters to other formats. It also improves the help output, and uses a new feature in CLI11 1.5 to fix an old "quirk" in the way unlimited options and positionals interact.
+This patch release adds better access to the App programmatically, to assist
+with writing custom converters to other formats. It also improves the help
+output, and uses a new feature in CLI11 1.5 to fix an old "quirk" in the way
+unlimited options and positionals interact.
 
-* Make mixing unlimited positionals and options more intuitive [#102][]
-* Add missing getters `get_options` and `get_description` to App [#105][]
-* The app name now can be set, and will override the auto name if present [#105][]
-* Add `(REQUIRED)` for required options [#104][]
-* Print simple name for Needs/Excludes [#104][]
-* Use Needs instead of Requires in help print [#104][]
-* Groups now are listed in the original definition order [#106][]
+- Make mixing unlimited positionals and options more intuitive [#102][]
+- Add missing getters `get_options` and `get_description` to App [#105][]
+- The app name now can be set, and will override the auto name if present
+  [#105][]
+- Add `(REQUIRED)` for required options [#104][]
+- Print simple name for Needs/Excludes [#104][]
+- Use Needs instead of Requires in help print [#104][]
+- Groups now are listed in the original definition order [#106][]
 
 [#102]: https://github.com/CLIUtils/CLI11/issues/102
 [#104]: https://github.com/CLIUtils/CLI11/pull/104
@@ -499,35 +748,44 @@ This patch release adds better access to the App programmatically, to assist wit
 
 ### Version 1.5.2: LICENSE in single header mode
 
-This is a quick patch release that makes LICENSE part of the single header file, making it easier to include. Minor cleanup from codacy. No significant code changes from 1.5.1.
+This is a quick patch release that makes LICENSE part of the single header file,
+making it easier to include. Minor cleanup from codacy. No significant code
+changes from 1.5.1.
 
 ### Version 1.5.3: Compiler compatibility
 
-This version fixes older AppleClang compilers by removing the optimization for casting. The minimum version of Boost Optional supported has been clarified to be 1.58. CUDA 7.0 NVCC is now supported.
+This version fixes older AppleClang compilers by removing the optimization for
+casting. The minimum version of Boost Optional supported has been clarified to
+be 1.58. CUDA 7.0 NVCC is now supported.
 
 ### Version 1.5.4: Optionals
 
-This version fixes the optional search in the single file version; some macros were not yet defined when it did the search. You can define the `CLI11_*_OPTIONAL` macros to 0 if needed to eliminate the search.
+This version fixes the optional search in the single file version; some macros
+were not yet defined when it did the search. You can define the
+`CLI11_*_OPTIONAL` macros to 0 if needed to eliminate the search.
 
 ## Version 1.4: More feedback
 
-This version adds lots of smaller fixes and additions after the refactor in version 1.3. More ways to download and use CLI11 in CMake have been added. INI files have improved support.
-
-* Lexical cast is now more strict than before [#68][] and fails on overflow [#84][]
-* Added `get_parent()` to access the parent from a subcommand
-* Added `ExistingPath` validator  [#73][]
-* `app.allow_ini_extras()` added to allow extras in INI files [#70][]
-* Multiline INI comments now supported
-* Descriptions can now be written with `config_to_str` [#66][]
-* Double printing of error message fixed [#77][]
-* Renamed `requires` to `needs` to avoid C++20 keyword [#75][], [#82][]
-* MakeSingleHeader now works if outside of git [#78][]
-* Adding install support for CMake [#79][], improved support for `find_package` [#83][], [#84][]
-* Added support for Conan.io [#83][]
+This version adds lots of smaller fixes and additions after the refactor in
+version 1.3. More ways to download and use CLI11 in CMake have been added. INI
+files have improved support.
+
+- Lexical cast is now more strict than before [#68][] and fails on overflow
+  [#84][]
+- Added `get_parent()` to access the parent from a subcommand
+- Added `ExistingPath` validator [#73][]
+- `app.allow_ini_extras()` added to allow extras in INI files [#70][]
+- Multiline INI comments now supported
+- Descriptions can now be written with `config_to_str` [#66][]
+- Double printing of error message fixed [#77][]
+- Renamed `requires` to `needs` to avoid C++20 keyword [#75][], [#82][]
+- MakeSingleHeader now works if outside of git [#78][]
+- Adding install support for CMake [#79][], improved support for `find_package`
+  [#83][], [#84][]
+- Added support for Conan.io [#83][]
 
 [#70]: https://github.com/CLIUtils/CLI11/issues/70
 [#75]: https://github.com/CLIUtils/CLI11/issues/75
-
 [#84]: https://github.com/CLIUtils/CLI11/pull/84
 [#83]: https://github.com/CLIUtils/CLI11/pull/83
 [#82]: https://github.com/CLIUtils/CLI11/pull/82
@@ -540,162 +798,237 @@ This version adds lots of smaller fixes and additions after the refactor in vers
 
 ## Version 1.3: Refactor
 
-This version focused on refactoring several key systems to ensure correct behavior in the interaction of different settings. Most caveats about
-features only working on the main App have been addressed, and extra arguments have been reworked. Inheritance
-of defaults makes configuring CLI11 much easier without having to subclass. Policies add new ways to handle multiple arguments to match your
-favorite CLI programs. Error messages and help messages are better and more flexible. Several bugs and odd behaviors in the parser have been fixed.
-
-* Added a version macro, `CLI11_VERSION`, along with `*_MAJOR`, `*_MINOR`, and `*_PATCH`, for programmatic access to the version.
-* Reworked the way defaults are set and inherited; explicit control given to user with `->option_defaults()` [#48](https://github.com/CLIUtils/CLI11/pull/48)
-* Hidden options now are based on an empty group name, instead of special "hidden" keyword [#48](https://github.com/CLIUtils/CLI11/pull/48)
-* `parse` no longer returns (so `CLI11_PARSE` is always usable) [#37](https://github.com/CLIUtils/CLI11/pull/37)
-* Added `remaining()` and `remaining_size()` [#37](https://github.com/CLIUtils/CLI11/pull/37)
-* `allow_extras` and `prefix_command` are now valid on subcommands [#37](https://github.com/CLIUtils/CLI11/pull/37)
-* Added `take_last` to only take last value passed [#40](https://github.com/CLIUtils/CLI11/pull/40)
-* Added `multi_option_policy` and shortcuts to provide more control than just a take last policy [#59](https://github.com/CLIUtils/CLI11/pull/59)
-* More detailed error messages in a few cases [#41](https://github.com/CLIUtils/CLI11/pull/41)
-* Footers can be added to help [#42](https://github.com/CLIUtils/CLI11/pull/42)
-* Help flags are easier to customize [#43](https://github.com/CLIUtils/CLI11/pull/43)
-* Subcommand now support groups [#46](https://github.com/CLIUtils/CLI11/pull/46)
-* `CLI::RuntimeError` added, for easy exit with error codes [#45](https://github.com/CLIUtils/CLI11/pull/45)
-* The clang-format script is now no longer "hidden" [#48](https://github.com/CLIUtils/CLI11/pull/48)
-* The order is now preserved for subcommands (list and callbacks) [#49](https://github.com/CLIUtils/CLI11/pull/49)
-* Tests now run individually, utilizing CMake 3.10 additions if possible [#50](https://github.com/CLIUtils/CLI11/pull/50)
-* Failure messages are now customizable, with a shorter default [#52](https://github.com/CLIUtils/CLI11/pull/52)
-* Some improvements to error codes [#53](https://github.com/CLIUtils/CLI11/pull/53)
-* `require_subcommand` now offers a two-argument form and negative values on the one-argument form are more useful [#51](https://github.com/CLIUtils/CLI11/pull/51)
-* Subcommands no longer match after the max required number is obtained [#51](https://github.com/CLIUtils/CLI11/pull/51)
-* Unlimited options no longer prioritize over remaining/unlimited positionals [#51](https://github.com/CLIUtils/CLI11/pull/51)
-* Added `->transform` which modifies the string parsed [#54](https://github.com/CLIUtils/CLI11/pull/54)
-* Changed of API in validators to `void(std::string &)` (const for users), throwing providing nicer errors [#54](https://github.com/CLIUtils/CLI11/pull/54)
-* Added `CLI::ArgumentMismatch` [#56](https://github.com/CLIUtils/CLI11/pull/56) and fixed missing failure if one arg expected [#55](https://github.com/CLIUtils/CLI11/issues/55)
-* Support for minimum unlimited expected arguments [#56](https://github.com/CLIUtils/CLI11/pull/56)
-* Single internal arg parse function [#56](https://github.com/CLIUtils/CLI11/pull/56)
-* Allow options to be disabled from INI file, rename `add_config` to `set_config` [#60](https://github.com/CLIUtils/CLI11/pull/60)
+This version focused on refactoring several key systems to ensure correct
+behavior in the interaction of different settings. Most caveats about features
+only working on the main App have been addressed, and extra arguments have been
+reworked. Inheritance of defaults makes configuring CLI11 much easier without
+having to subclass. Policies add new ways to handle multiple arguments to match
+your favorite CLI programs. Error messages and help messages are better and more
+flexible. Several bugs and odd behaviors in the parser have been fixed.
+
+- Added a version macro, `CLI11_VERSION`, along with `*_MAJOR`, `*_MINOR`, and
+  `*_PATCH`, for programmatic access to the version.
+- Reworked the way defaults are set and inherited; explicit control given to
+  user with `->option_defaults()`
+  [#48](https://github.com/CLIUtils/CLI11/pull/48)
+- Hidden options now are based on an empty group name, instead of special
+  "hidden" keyword [#48](https://github.com/CLIUtils/CLI11/pull/48)
+- `parse` no longer returns (so `CLI11_PARSE` is always usable)
+  [#37](https://github.com/CLIUtils/CLI11/pull/37)
+- Added `remaining()` and `remaining_size()`
+  [#37](https://github.com/CLIUtils/CLI11/pull/37)
+- `allow_extras` and `prefix_command` are now valid on subcommands
+  [#37](https://github.com/CLIUtils/CLI11/pull/37)
+- Added `take_last` to only take last value passed
+  [#40](https://github.com/CLIUtils/CLI11/pull/40)
+- Added `multi_option_policy` and shortcuts to provide more control than just a
+  take last policy [#59](https://github.com/CLIUtils/CLI11/pull/59)
+- More detailed error messages in a few cases
+  [#41](https://github.com/CLIUtils/CLI11/pull/41)
+- Footers can be added to help [#42](https://github.com/CLIUtils/CLI11/pull/42)
+- Help flags are easier to customize
+  [#43](https://github.com/CLIUtils/CLI11/pull/43)
+- Subcommand now support groups [#46](https://github.com/CLIUtils/CLI11/pull/46)
+- `CLI::RuntimeError` added, for easy exit with error codes
+  [#45](https://github.com/CLIUtils/CLI11/pull/45)
+- The clang-format script is now no longer "hidden"
+  [#48](https://github.com/CLIUtils/CLI11/pull/48)
+- The order is now preserved for subcommands (list and callbacks)
+  [#49](https://github.com/CLIUtils/CLI11/pull/49)
+- Tests now run individually, utilizing CMake 3.10 additions if possible
+  [#50](https://github.com/CLIUtils/CLI11/pull/50)
+- Failure messages are now customizable, with a shorter default
+  [#52](https://github.com/CLIUtils/CLI11/pull/52)
+- Some improvements to error codes
+  [#53](https://github.com/CLIUtils/CLI11/pull/53)
+- `require_subcommand` now offers a two-argument form and negative values on the
+  one-argument form are more useful
+  [#51](https://github.com/CLIUtils/CLI11/pull/51)
+- Subcommands no longer match after the max required number is obtained
+  [#51](https://github.com/CLIUtils/CLI11/pull/51)
+- Unlimited options no longer prioritize over remaining/unlimited positionals
+  [#51](https://github.com/CLIUtils/CLI11/pull/51)
+- Added `->transform` which modifies the string parsed
+  [#54](https://github.com/CLIUtils/CLI11/pull/54)
+- Changed of API in validators to `void(std::string &)` (const for users),
+  throwing providing nicer errors
+  [#54](https://github.com/CLIUtils/CLI11/pull/54)
+- Added `CLI::ArgumentMismatch` [#56](https://github.com/CLIUtils/CLI11/pull/56)
+  and fixed missing failure if one arg expected
+  [#55](https://github.com/CLIUtils/CLI11/issues/55)
+- Support for minimum unlimited expected arguments
+  [#56](https://github.com/CLIUtils/CLI11/pull/56)
+- Single internal arg parse function
+  [#56](https://github.com/CLIUtils/CLI11/pull/56)
+- Allow options to be disabled from INI file, rename `add_config` to
+  `set_config` [#60](https://github.com/CLIUtils/CLI11/pull/60)
 
 > ### Converting from CLI11 1.2
 >
-> * `app.parse` no longer returns a vector. Instead, use `app.remaining(true)`.
-> * `"hidden"` is no longer a special group name, instead use `""`
-> * Validators API has changed to return an error string; use `.empty()` to get the old bool back
-> * Use `.set_help_flag` instead of accessing the help pointer directly (discouraged, but not removed yet)
-> * `add_config` has been renamed to `set_config`
-> * Errors thrown in some cases are slightly more specific
+> - `app.parse` no longer returns a vector. Instead, use `app.remaining(true)`.
+> - `"hidden"` is no longer a special group name, instead use `""`
+> - Validators API has changed to return an error string; use `.empty()` to get
+>   the old bool back
+> - Use `.set_help_flag` instead of accessing the help pointer directly
+>   (discouraged, but not removed yet)
+> - `add_config` has been renamed to `set_config`
+> - Errors thrown in some cases are slightly more specific
 
 ## Version 1.2: Stability
 
-This release focuses on making CLI11 behave properly in corner cases, and with config files on the command line. This includes fixes for a variety of reported issues. A few features were added to make life easier, as well; such as a new flag callback and a macro for the parse command.
-
-* Added functional form of flag [#33](https://github.com/CLIUtils/CLI11/pull/33), automatic on C++14
-* Fixed Config file search if passed on command line [#30](https://github.com/CLIUtils/CLI11/issues/30)
-* Added `CLI11_PARSE(app, argc, argv)` macro for simple parse commands (does not support returning arg)
-* The name string can now contain spaces around commas [#29](https://github.com/CLIUtils/CLI11/pull/29)
-* `set_default_str` now only sets string, and `set_default_val` will evaluate the default string given [#26](https://github.com/CLIUtils/CLI11/issues/26)
-* Required positionals now take priority over subcommands [#23](https://github.com/CLIUtils/CLI11/issues/23)
-* Extra requirements enforced by Travis
+This release focuses on making CLI11 behave properly in corner cases, and with
+config files on the command line. This includes fixes for a variety of reported
+issues. A few features were added to make life easier, as well; such as a new
+flag callback and a macro for the parse command.
+
+- Added functional form of flag
+  [#33](https://github.com/CLIUtils/CLI11/pull/33), automatic on C++14
+- Fixed Config file search if passed on command line
+  [#30](https://github.com/CLIUtils/CLI11/issues/30)
+- Added `CLI11_PARSE(app, argc, argv)` macro for simple parse commands (does not
+  support returning arg)
+- The name string can now contain spaces around commas
+  [#29](https://github.com/CLIUtils/CLI11/pull/29)
+- `set_default_str` now only sets string, and `set_default_val` will evaluate
+  the default string given [#26](https://github.com/CLIUtils/CLI11/issues/26)
+- Required positionals now take priority over subcommands
+  [#23](https://github.com/CLIUtils/CLI11/issues/23)
+- Extra requirements enforced by Travis
 
 ## Version 1.1: Feedback
 
-This release incorporates feedback from the release announcement. The examples are slowly being expanded, some corner cases improved, and some new functionality for tricky parsing situations.
-
-* Added simple support for enumerations, allow non-printable objects [#12](https://github.com/CLIUtils/CLI11/issues/12)
-* Added `app.parse_order()` with original parse order ([#13](https://github.com/CLIUtils/CLI11/issues/13), [#16](https://github.com/CLIUtils/CLI11/pull/16))
-* Added `prefix_command()`, which is like `allow_extras` but instantly stops and returns. ([#8](https://github.com/CLIUtils/CLI11/issues/8), [#17](https://github.com/CLIUtils/CLI11/pull/17))
-* Removed Windows warning ([#10](https://github.com/CLIUtils/CLI11/issues/10), [#20](https://github.com/CLIUtils/CLI11/pull/20))
-* Some improvements to CMake, detect Python and no dependencies on Python 2 (like Python 3) ([#18](https://github.com/CLIUtils/CLI11/issues/18), [#21](https://github.com/CLIUtils/CLI11/pull/21))
+This release incorporates feedback from the release announcement. The examples
+are slowly being expanded, some corner cases improved, and some new
+functionality for tricky parsing situations.
+
+- Added simple support for enumerations, allow non-printable objects
+  [#12](https://github.com/CLIUtils/CLI11/issues/12)
+- Added `app.parse_order()` with original parse order
+  ([#13](https://github.com/CLIUtils/CLI11/issues/13),
+  [#16](https://github.com/CLIUtils/CLI11/pull/16))
+- Added `prefix_command()`, which is like `allow_extras` but instantly stops and
+  returns. ([#8](https://github.com/CLIUtils/CLI11/issues/8),
+  [#17](https://github.com/CLIUtils/CLI11/pull/17))
+- Removed Windows warning ([#10](https://github.com/CLIUtils/CLI11/issues/10),
+  [#20](https://github.com/CLIUtils/CLI11/pull/20))
+- Some improvements to CMake, detect Python and no dependencies on Python 2
+  (like Python 3) ([#18](https://github.com/CLIUtils/CLI11/issues/18),
+  [#21](https://github.com/CLIUtils/CLI11/pull/21))
 
 ## Version 1.0: Official release
 
-This is the first stable release for CLI11. Future releases will try to remain backward compatible and will follow semantic versioning if possible. There were a few small changes since version 0.9:
+This is the first stable release for CLI11. Future releases will try to remain
+backward compatible and will follow semantic versioning if possible. There were
+a few small changes since version 0.9:
 
-* Cleanup using `clang-tidy` and `clang-format`
-* Small improvements to Timers, easier to subclass Error
-* Move to 3-Clause BSD license
+- Cleanup using `clang-tidy` and `clang-format`
+- Small improvements to Timers, easier to subclass Error
+- Move to 3-Clause BSD license
 
 ## Version 0.9: Polish
 
-This release focused on cleaning up the most exotic compiler warnings, fixing a few oddities of the config parser, and added a more natural method to check subcommands.
+This release focused on cleaning up the most exotic compiler warnings, fixing a
+few oddities of the config parser, and added a more natural method to check
+subcommands.
 
-* Better CMake named target (CLI11)
-* More warnings added, fixed
-* Ini output now includes `=false` when `default_also` is true
-* Ini no longer lists the help pointer
-* Added test for inclusion in multiple files and linking, fixed issues (rarely needed for CLI, but nice for tools)
-* Support for complex numbers
-* Subcommands now test true/false directly or with `->parsed()`, cleaner parse
+- Better CMake named target (CLI11)
+- More warnings added, fixed
+- Ini output now includes `=false` when `default_also` is true
+- Ini no longer lists the help pointer
+- Added test for inclusion in multiple files and linking, fixed issues (rarely
+  needed for CLI, but nice for tools)
+- Support for complex numbers
+- Subcommands now test true/false directly or with `->parsed()`, cleaner parse
 
 ## Version 0.8: CLIUtils
 
 This release moved the repository to the CLIUtils main organization.
 
-* Moved to CLIUtils on GitHub
-* Fixed docs build and a few links
+- Moved to CLIUtils on GitHub
+- Fixed docs build and a few links
 
 ## Version 0.7: Code coverage 100%
 
-Lots of small bugs fixed when adding code coverage, better in edge cases. Much more powerful ini support.
+Lots of small bugs fixed when adding code coverage, better in edge cases. Much
+more powerful ini support.
 
-* Allow comments in ini files (lines starting with `;`)
-* Ini files support flags, vectors, subcommands
-* Added CodeCov code coverage reports
-* Lots of small bugfixes related to adding tests to increase coverage to 100%
-* Error handling now uses scoped enum in errors
-* Reparsing rules changed a little to accommodate Ini files. Callbacks are now called when parsing INI, and reset any time results are added.
-* Adding extra utilities in full version only, `Timer` (not needed for parsing, but useful for general CLI applications).
-* Better support for custom `add_options` like functions.
+- Allow comments in ini files (lines starting with `;`)
+- Ini files support flags, vectors, subcommands
+- Added CodeCov code coverage reports
+- Lots of small bugfixes related to adding tests to increase coverage to 100%
+- Error handling now uses scoped enum in errors
+- Reparsing rules changed a little to accommodate Ini files. Callbacks are now
+  called when parsing INI, and reset any time results are added.
+- Adding extra utilities in full version only, `Timer` (not needed for parsing,
+  but useful for general CLI applications).
+- Better support for custom `add_options` like functions.
 
 ## Version 0.6: Cleanup
 
-Lots of cleanup and docs additions made it into this release. Parsing is simpler and more robust; fall through option added and works as expected; much more consistent variable names internally.
+Lots of cleanup and docs additions made it into this release. Parsing is simpler
+and more robust; fall through option added and works as expected; much more
+consistent variable names internally.
 
-* Simplified parsing to use `vector<string>` only
-* Fixed fallthrough, made it optional as well (default: off): `.fallthrough()`.
-* Added string versions of `->requires()` and `->excludes()` for consistency.
-* Renamed protected members for internal consistency, grouped docs.
-* Added the ability to add a number to `.require_subcommand()`.
+- Simplified parsing to use `vector<string>` only
+- Fixed fallthrough, made it optional as well (default: off): `.fallthrough()`.
+- Added string versions of `->requires()` and `->excludes()` for consistency.
+- Renamed protected members for internal consistency, grouped docs.
+- Added the ability to add a number to `.require_subcommand()`.
 
 ## Version 0.5: Windows support
 
-* Allow `Hidden` options.
-* Throw `OptionAlreadyAdded` errors for matching subcommands or options, with ignore-case included, tests
-* `->ignore_case()` added to subcommands, options, and `add_set_ignore_case`. Subcommands inherit setting from parent App on creation.
-* Subcommands now can be "chained", that is, left over arguments can now include subcommands that then get parsed. Subcommands are now a list (`get_subcommands`). Added `got_subcommand(App_or_name)` to check for subcommands.
-* Added `.allow_extras()` to disable error on failure. Parse returns a vector of leftover options. Renamed error to `ExtrasError`, and now triggers on extra options too.
-* Added `require_subcommand` to `App`, to simplify forcing subcommands. Do **not** do `add_subcommand()->require_subcommand`, since that is the subcommand, not the main `App`.
-* Added printout of ini file text given parsed options, skips flags.
-* Support for quotes and spaces in ini files
-* Fixes to allow support for Windows (added Appveyor) (Uses `-`, not `/` syntax)
+- Allow `Hidden` options.
+- Throw `OptionAlreadyAdded` errors for matching subcommands or options, with
+  ignore-case included, tests
+- `->ignore_case()` added to subcommands, options, and `add_set_ignore_case`.
+  Subcommands inherit setting from parent App on creation.
+- Subcommands now can be "chained", that is, left over arguments can now include
+  subcommands that then get parsed. Subcommands are now a list
+  (`get_subcommands`). Added `got_subcommand(App_or_name)` to check for
+  subcommands.
+- Added `.allow_extras()` to disable error on failure. Parse returns a vector of
+  leftover options. Renamed error to `ExtrasError`, and now triggers on extra
+  options too.
+- Added `require_subcommand` to `App`, to simplify forcing subcommands. Do
+  **not** do `add_subcommand()->require_subcommand`, since that is the
+  subcommand, not the main `App`.
+- Added printout of ini file text given parsed options, skips flags.
+- Support for quotes and spaces in ini files
+- Fixes to allow support for Windows (added Appveyor) (Uses `-`, not `/` syntax)
 
 ## Version 0.4: Ini support
 
-* Updates to help print
-* Removed `run`, please use `parse` unless you subclass and add it
-* Supports ini files mixed with command line, tested
-* Added Range for further Plumbum compatibility
-* Added function to print out ini file
+- Updates to help print
+- Removed `run`, please use `parse` unless you subclass and add it
+- Supports ini files mixed with command line, tested
+- Added Range for further Plumbum compatibility
+- Added function to print out ini file
 
 ## Version 0.3: Plumbum compatibility
 
-* Added `->requires`, `->excludes`, and `->envname` from [Plumbum](http://plumbum.readthedocs.io/en/latest/)
-* Supports `->mandatory` from Plumbum
-* More tests for help strings, improvements in formatting
-* Support type and set syntax in positionals help strings
-* Added help groups, with `->group("name")` syntax
-* Added initial support for ini file reading with `add_config` option.
-* Supports GCC 4.7 again
-* Clang 3.5 now required for tests due to googlemock usage, 3.4 should still work otherwise
-* Changes `setup` for an explicit help bool in constructor/`add_subcommand`
+- Added `->requires`, `->excludes`, and `->envname` from
+  [Plumbum](http://plumbum.readthedocs.io/en/latest/)
+- Supports `->mandatory` from Plumbum
+- More tests for help strings, improvements in formatting
+- Support type and set syntax in positionals help strings
+- Added help groups, with `->group("name")` syntax
+- Added initial support for ini file reading with `add_config` option.
+- Supports GCC 4.7 again
+- Clang 3.5 now required for tests due to googlemock usage, 3.4 should still
+  work otherwise
+- Changes `setup` for an explicit help bool in constructor/`add_subcommand`
 
 ## Version 0.2: Leaner and meaner
 
-* Moved to simpler syntax, where `Option` pointers are returned and operated on
-* Removed `make_` style options
-* Simplified Validators, now only requires `->check(function)`
-* Removed Combiners
-* Fixed pointers to Options, stored in `unique_ptr` now
-* Added `Option_p` and `App_p`, mostly for internal use
-* Startup sequence, including help flag, can be modified by subclasses
+- Moved to simpler syntax, where `Option` pointers are returned and operated on
+- Removed `make_` style options
+- Simplified Validators, now only requires `->check(function)`
+- Removed Combiners
+- Fixed pointers to Options, stored in `unique_ptr` now
+- Added `Option_p` and `App_p`, mostly for internal use
+- Startup sequence, including help flag, can be modified by subclasses
 
 ## Version 0.1: First release
 
-First release before major cleanup. Still has make syntax and combiners; very clever syntax but not the best or most commonly expected way to work.
+First release before major cleanup. Still has make syntax and combiners; very
+clever syntax but not the best or most commonly expected way to work.
diff --git a/CLI11.hpp.in b/CLI11.hpp.in
index 9439a43..83f228e 100644
--- a/CLI11.hpp.in
+++ b/CLI11.hpp.in
@@ -5,7 +5,7 @@
 // This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts
 // from: {git}
 //
-// CLI11 {version} Copyright (c) 2017-2021 University of Cincinnati, developed by Henry
+// CLI11 {version} Copyright (c) 2017-2022 University of Cincinnati, developed by Henry
 // Schreiner under NSF AWARD 1414736. All rights reserved.
 //
 // Redistribution and use in source and binary forms of CLI11, with or without
@@ -46,24 +46,38 @@ namespace {namespace} {{
 
 {string_tools_hpp}
 
+{string_tools_inl_hpp}
+
 {error_hpp}
 
 {type_tools_hpp}
 
 {split_hpp}
 
+{split_inl_hpp}
+
 {config_fwd_hpp}
 
 {validators_hpp}
 
+{validators_inl_hpp}
+
 {formatter_fwd_hpp}
 
 {option_hpp}
 
+{option_inl_hpp}
+
 {app_hpp}
 
+{app_inl_hpp}
+
 {config_hpp}
 
+{config_inl_hpp}
+
 {formatter_hpp}
 
+{formatter_inl_hpp}
+
 }} // namespace {namespace}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1f4313f..bad2b00 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,10 +6,10 @@ cmake_minimum_required(VERSION 3.4)
 # of CMake. For most of the policies, the new version is better (hence the change).
 # We don't use the 3.4...3.21 syntax because of a bug in an older MSVC's
 # built-in and modified CMake 3.11
-if(${CMAKE_VERSION} VERSION_LESS 3.22)
+if(${CMAKE_VERSION} VERSION_LESS 3.24)
   cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
 else()
-  cmake_policy(VERSION 3.22)
+  cmake_policy(VERSION 3.24)
 endif()
 
 set(VERSION_REGEX "#define CLI11_VERSION[ \t]+\"(.+)\"")
@@ -27,6 +27,8 @@ project(
   LANGUAGES CXX
   VERSION ${VERSION_STRING})
 
+list(APPEND CMAKE_MODULE_PATH "${CLI11_SOURCE_DIR}/cmake")
+
 # Print the version number of CMake if this is the main project
 if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
   message(STATUS "CMake ${CMAKE_VERSION}")
@@ -77,6 +79,7 @@ endif()
 
 option(CLI11_WARNINGS_AS_ERRORS "Turn all warnings into errors (for CI)")
 option(CLI11_SINGLE_FILE "Generate a single header file")
+option(CLI11_PRECOMPILED "Generate a precompiled static library instead of a header-only" OFF)
 cmake_dependent_option(CLI11_SANITIZERS "Download the sanitizers CMake config" OFF
                        "NOT CMAKE_VERSION VERSION_LESS 3.11" OFF)
 
@@ -105,12 +108,10 @@ cmake_dependent_option(
   CLI11_CUDA_TESTS "Build the tests with NVCC to check for warnings there - requires CMake 3.9+"
   OFF "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME" OFF)
 
-cmake_dependent_option(
-  CLI11_CLANG_TIDY "Look for and use Clang-Tidy" OFF
-  "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME;NOT CMAKE_VERSION VERSION_LESS 3.6" OFF)
-set(CLI11_CLANG_TIDY_OPTIONS
-    ""
-    CACHE STRING "Clang tidy options, such as -fix, semicolon separated")
+if(CLI11_PRECOMPILED AND CLI11_SINGLE_FILE)
+  # Sanity check
+  message(FATAL_ERROR "CLI11_PRECOMPILE and CLI11_SINGLE_FILE are mutually exclusive")
+endif()
 
 if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND NOT DEFINED CMAKE_CXX_STANDARD)
   set(CMAKE_CXX_STANDARD 11)
@@ -129,59 +130,9 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
   set_property(GLOBAL PROPERTY USE_FOLDERS ON)
 endif()
 
-# Special target that adds warnings. Is not exported.
-add_library(CLI11_warnings INTERFACE)
-
-set(unix-warnings -Wall -Wextra -pedantic -Wshadow -Wsign-conversion -Wswitch-enum)
-
-# Buggy in GCC 4.8
-if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9)
-  list(APPEND unix-warnings -Weffc++)
-endif()
-
-target_compile_options(
-  CLI11_warnings
-  INTERFACE $<$<BOOL:${CLI11_FORCE_LIBCXX}>:-stdlib=libc++>
-            $<$<CXX_COMPILER_ID:MSVC>:/W4
-            $<$<BOOL:${CLI11_WARNINGS_AS_ERRORS}>:/WX>>
-            $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:${unix-warnings}
-            $<$<BOOL:${CLI11_WARNINGS_AS_ERRORS}>:-Werror>>)
-
-if(NOT CMAKE_VERSION VERSION_LESS 3.13)
-  target_link_options(CLI11_warnings INTERFACE $<$<BOOL:${CLI11_FORCE_LIBCXX}>:-stdlib=libc++>)
-endif()
+include(CLI11Warnings)
 
-# Allow IDE's to group targets into folders
-add_library(CLI11 INTERFACE)
-add_library(CLI11::CLI11 ALIAS CLI11) # for add_subdirectory calls
-
-# Duplicated because CMake adds the current source dir if you don't.
-target_include_directories(CLI11 INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
-                                           $<INSTALL_INTERFACE:include>)
-
-if(CMAKE_VERSION VERSION_LESS 3.8)
-  # This might not be a complete list
-  target_compile_features(
-    CLI11
-    INTERFACE cxx_lambdas
-              cxx_nullptr
-              cxx_override
-              cxx_range_for
-              cxx_right_angle_brackets
-              cxx_strong_enums
-              cxx_constexpr
-              cxx_auto_type)
-else()
-  target_compile_features(CLI11 INTERFACE cxx_std_11)
-endif()
-
-# To see in IDE, headers must be listed for target
-set(header-patterns "${PROJECT_SOURCE_DIR}/include/CLI/*")
-if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND NOT CMAKE_VERSION VERSION_LESS 3.12)
-  list(INSERT header-patterns 0 CONFIGURE_DEPENDS)
-endif()
-
-file(GLOB CLI11_headers ${header-patterns})
+add_subdirectory(src)
 
 # Allow tests to be run on CUDA
 if(CLI11_CUDA_TESTS)
@@ -191,22 +142,8 @@ if(CLI11_CUDA_TESTS)
   set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcudafe --display_error_number")
 endif()
 
-# Prepare Clang-Tidy
-if(CLI11_CLANG_TIDY)
-  find_program(
-    CLANG_TIDY_EXE
-    NAMES "clang-tidy"
-    DOC "Path to clang-tidy executable" REQUIRED)
-
-  set(DO_CLANG_TIDY "${CLANG_TIDY_EXE}" ${CLI11_CLANG_TIDY_OPTIONS})
-endif()
-
 # This folder should be installed
 if(CLI11_INSTALL)
-  install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
-
-  # Make an export target
-  install(TARGETS CLI11 EXPORT CLI11Targets)
 
   # Use find_package on the installed package
   # Since we have no custom code, we can directly write this
@@ -239,41 +176,6 @@ if(CLI11_INSTALL)
   export(PACKAGE CLI11)
 endif()
 
-if(CLI11_SINGLE_FILE)
-  # Single file test
-  if(CMAKE_VERSION VERSION_LESS 3.12)
-    find_package(PythonInterp REQUIRED)
-    add_executable(Python::Interpreter IMPORTED)
-    set_target_properties(Python::Interpreter PROPERTIES IMPORTED_LOCATION "${PYTHON_EXECUTABLE}"
-                                                         VERSION "${PYTHON_VERSION_STRING}")
-  else()
-    find_package(
-      Python
-      COMPONENTS Interpreter
-      REQUIRED)
-  endif()
-
-  file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include")
-  add_custom_command(
-    OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp"
-    COMMAND
-      Python::Interpreter "${CMAKE_CURRENT_SOURCE_DIR}/scripts/MakeSingleHeader.py"
-      ${CLI11_headers} --main "${CMAKE_CURRENT_SOURCE_DIR}/CLI11.hpp.in" --output
-      "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" --version "${CLI11_VERSION}"
-    DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" ${CLI11_headers})
-  add_custom_target(CLI11-generate-single-file ALL
-                    DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp")
-  set_property(TARGET CLI11-generate-single-file PROPERTY FOLDER "Scripts")
-  install(FILES "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" DESTINATION include)
-  add_library(CLI11_SINGLE INTERFACE)
-  target_link_libraries(CLI11_SINGLE INTERFACE CLI11)
-  add_dependencies(CLI11_SINGLE CLI11-generate-single-file)
-  target_compile_definitions(CLI11_SINGLE INTERFACE -DCLI11_SINGLE_FILE)
-  target_include_directories(
-    CLI11_SINGLE INTERFACE $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include/>
-                           $<INSTALL_INTERFACE:include>)
-endif()
-
 if(CLI11_BUILD_TESTS)
   include(CTest)
   add_subdirectory(tests)
diff --git a/LICENSE b/LICENSE
index 17739d1..aae1585 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-CLI11 1.8 Copyright (c) 2017-2019 University of Cincinnati, developed by Henry
+CLI11 2.2 Copyright (c) 2017-2023 University of Cincinnati, developed by Henry
 Schreiner under NSF AWARD 1414736. All rights reserved.
 
 Redistribution and use in source and binary forms of CLI11, with or without
diff --git a/README.md b/README.md
index ad1ac1e..f08094d 100644
--- a/README.md
+++ b/README.md
@@ -7,8 +7,7 @@
 [![Build Status AppVeyor][appveyor-badge]][appveyor]
 [![Code Coverage][codecov-badge]][codecov]
 [![Codacy Badge][codacy-badge]][codacy-link]
-[![License: BSD][license-badge]](./LICENSE)
-[![DOI][doi-badge]][doi-link]
+[![License: BSD][license-badge]](./LICENSE) [![DOI][doi-badge]][doi-link]
 
 [![Gitter chat][gitter-badge]][gitter]
 [![Latest GHA release][releases-badge]][github releases]
@@ -17,78 +16,101 @@
 [![Conda Version][conda-badge]][conda-link]
 [![Try CLI11 2.1 online][wandbox-badge]][wandbox-link]
 
-[What's new](./CHANGELOG.md) •
-[Documentation][gitbook] •
-[API Reference][api-docs]
+[What's new](./CHANGELOG.md) • [Documentation][gitbook] • [API
+Reference][api-docs]
 
-CLI11 is a command line parser for C++11 and beyond that provides a rich feature set with a simple and intuitive interface.
+CLI11 is a command line parser for C++11 and beyond that provides a rich feature
+set with a simple and intuitive interface.
 
 ## Table of Contents
 
-* [Background](#background)
-  * [Introduction](#introduction)
-  * [Why write another CLI parser?](#why-write-another-cli-parser)
-  * [Other parsers](#other-parsers)
-  * [Features not supported by this library](#features-not-supported-by-this-library)
-* [Install](#install)
-* [Usage](#usage)
-  * [Adding options](#adding-options)
-    * [Option types](#option-types)
-    * [Example](#example)
-    * [Option options](#option-options)
-    * [Validators](#validators)
-      * [Transforming Validators](#transforming-validators)
-      * [Validator operations](#validator-operations)
-      * [Custom Validators](#custom-validators)
-      * [Querying Validators](#querying-validators)
-      * [Getting Results](#getting-results)
-  * [Subcommands](#subcommands)
-    * [Subcommand options](#subcommand-options)
-    * [Option groups](#option-groups)
-    * [Callbacks](#callbacks)
-  * [Configuration file](#configuration-file)
-  * [Inheriting defaults](#inheriting-defaults)
-  * [Formatting](#formatting)
-  * [Subclassing](#subclassing)
-  * [How it works](#how-it-works)
-  * [Utilities](#utilities)
-  * [Other libraries](#other-libraries)
-* [API](#api)
-* [Examples](#Examples)
-* [Contribute](#contribute)
-* [License](#license)
-
-Features that were added in the last released major version are marked with "🆕". Features only available in main are marked with "🚧".
+- [Background](#background)
+  - [Introduction](#introduction)
+  - [Why write another CLI parser?](#why-write-another-cli-parser)
+  - [Other parsers](#other-parsers)
+  - [Features not supported by this library](#features-not-supported-by-this-library)
+- [Install](#install)
+- [Usage](#usage)
+  - [Adding options](#adding-options)
+    - [Option types](#option-types)
+    - [Example](#example)
+    - [Option options](#option-options)
+    - [Validators](#validators)
+      - [Transforming Validators](#transforming-validators)
+      - [Validator operations](#validator-operations)
+      - [Custom Validators](#custom-validators)
+      - [Querying Validators](#querying-validators)
+      - [Getting Results](#getting-results)
+  - [Subcommands](#subcommands)
+    - [Subcommand options](#subcommand-options)
+    - [Option groups](#option-groups)
+    - [Callbacks](#callbacks)
+  - [Configuration file](#configuration-file)
+  - [Inheriting defaults](#inheriting-defaults)
+  - [Formatting](#formatting)
+  - [Subclassing](#subclassing)
+  - [How it works](#how-it-works)
+  - [Utilities](#utilities)
+  - [Other libraries](#other-libraries)
+- [API](#api)
+- [Examples](#Examples)
+- [Contribute](#contribute)
+- [License](#license)
+
+Features that were added in the last released minor version are marked with
+"🆕". Features only available in main are marked with "🚧".
 
 ## Background
 
 ### Introduction
 
-CLI11 provides all the features you expect in a powerful command line parser, with a beautiful, minimal syntax and no dependencies beyond C++11. It is header only, and comes in a single file form for easy inclusion in projects. It is easy to use for small projects, but powerful enough for complex command line projects, and can be customized for frameworks.
-It is tested on [Azure][] and [GitHub Actions][actions-link], and was originally used by the [GooFit GPU fitting framework][goofit]. It was inspired by [`plumbum.cli`][plumbum] for Python. CLI11 has a user friendly introduction in this README, a more in-depth tutorial [GitBook][], as well as [API documentation][api-docs] generated by Travis.
-See the [changelog](./CHANGELOG.md) or [GitHub Releases][] for details for current and past releases. Also see the [Version 1.0 post][], [Version 1.3 post][], [Version 1.6 post][], or [Version 2.0 post][] for more information.
-
-You can be notified when new releases are made by subscribing to <https://github.com/CLIUtils/CLI11/releases.atom> on an RSS reader, like Feedly, or use the releases mode of the GitHub watching tool.
+CLI11 provides all the features you expect in a powerful command line parser,
+with a beautiful, minimal syntax and no dependencies beyond C++11. It is header
+only, and comes in a single file form for easy inclusion in projects. It is easy
+to use for small projects, but powerful enough for complex command line
+projects, and can be customized for frameworks. It is tested on [Azure][] and
+[GitHub Actions][actions-link], and was originally used by the [GooFit GPU
+fitting framework][goofit]. It was inspired by [`plumbum.cli`][plumbum] for
+Python. CLI11 has a user friendly introduction in this README, a more in-depth
+tutorial [GitBook][], as well as [API documentation][api-docs] generated by
+Travis. See the [changelog](./CHANGELOG.md) or [GitHub Releases][] for details
+for current and past releases. Also see the [Version 1.0 post][], [Version 1.3
+post][], [Version 1.6 post][], or [Version 2.0 post][] for more information.
+
+You can be notified when new releases are made by subscribing to
+<https://github.com/CLIUtils/CLI11/releases.atom> on an RSS reader, like Feedly,
+or use the releases mode of the GitHub watching tool.
 
 ### Why write another CLI parser?
 
 An acceptable CLI parser library should be all of the following:
 
-* Easy to include (i.e., header only, one file if possible, **no external requirements**).
-* Short, simple syntax: This is one of the main reasons to use a CLI parser, it should make variables from the command line nearly as easy to define as any other variables. If most of your program is hidden in CLI parsing, this is a problem for readability.
-* C++11 or better: Should work with GCC 4.8+ (default on CentOS/RHEL 7), Clang 3.4+, AppleClang 7+, NVCC 7.0+, or MSVC 2015+.
-* Work on Linux, macOS, and Windows.
-* Well tested on all common platforms and compilers. "Well" is defined as having good coverage measured by [CodeCov][].
-* Clear help printing.
-* Nice error messages.
-* Standard shell idioms supported naturally, like grouping flags, a positional separator, etc.
-* Easy to execute, with help, parse errors, etc. providing correct exit and details.
-* Easy to extend as part of a framework that provides "applications" to users.
-* Usable subcommand syntax, with support for multiple subcommands, nested subcommands, option groups, and optional fallthrough (explained later).
-* Ability to add a configuration file (`TOML`, `INI`, or custom format), and produce it as well.
-* Produce real values that can be used directly in code, not something you have pay compute time to look up, for HPC applications.
-* Work with standard types, simple custom types, and extensible to exotic types.
-* Permissively licensed.
+- Easy to include (i.e., header only, one file if possible, **no external
+  requirements**).
+- Short, simple syntax: This is one of the main reasons to use a CLI parser, it
+  should make variables from the command line nearly as easy to define as any
+  other variables. If most of your program is hidden in CLI parsing, this is a
+  problem for readability.
+- C++11 or better: Should work with GCC 4.8+ (default on CentOS/RHEL 7), Clang
+  3.4+, AppleClang 7+, NVCC 7.0+, or MSVC 2015+.
+- Work on Linux, macOS, and Windows.
+- Well tested on all common platforms and compilers. "Well" is defined as having
+  good coverage measured by [CodeCov][].
+- Clear help printing.
+- Nice error messages.
+- Standard shell idioms supported naturally, like grouping flags, a positional
+  separator, etc.
+- Easy to execute, with help, parse errors, etc. providing correct exit and
+  details.
+- Easy to extend as part of a framework that provides "applications" to users.
+- Usable subcommand syntax, with support for multiple subcommands, nested
+  subcommands, option groups, and optional fallthrough (explained later).
+- Ability to add a configuration file (`TOML`, `INI`, or custom format), and
+  produce it as well.
+- Produce real values that can be used directly in code, not something you have
+  pay compute time to look up, for HPC applications.
+- Work with common types, simple custom types, and extensible to exotic types.
+- Permissively licensed.
 
 ### Other parsers
 
@@ -110,37 +132,64 @@ After I wrote this, I also found the following libraries:
 | [GetOpt][]              | Very limited C solution with long, convoluted syntax. Does not support much of anything, like help generation. Always available on UNIX, though (but in different flavors).          |
 | [ProgramOptions.hxx][]  | Interesting library, less powerful and no subcommands. Nice callback system.                                                                                                         |
 | [Args][]                | Also interesting, and supports subcommands. I like the optional-like design, but CLI11 is cleaner and provides direct value access, and is less verbose.                             |
-| [Argument Aggregator][] | I'm a big fan of the [fmt][] library, and the try-catch statement looks familiar.  :thumbsup: Doesn't seem to support subcommands.                                                   |
+| [Argument Aggregator][] | I'm a big fan of the [fmt][] library, and the try-catch statement looks familiar. :thumbsup: Doesn't seem to support subcommands.                                                    |
 | [Clara][]               | Simple library built for the excellent [Catch][] testing framework. Unique syntax, limited scope.                                                                                    |
 | [Argh!][]               | Very minimalistic C++11 parser, single header. Don't have many features. No help generation?!?! At least it's exception-free.                                                        |
 | [CLI][]                 | Custom language and parser. Huge build-system overkill for very little benefit. Last release in 2009, but still occasionally active.                                                 |
 | [argparse][]            | C++17 single file argument parser. Design seems similar to CLI11 in some ways. The author has several other interesting projects.                                                    |
+| [lyra][]                | a simple header only parser with composable options. Might work well for simple standardized parsing                                                                                 |
 
-See [Awesome C++][] for a less-biased list of parsers. You can also find other single file libraries at [Single file libs][].
+See [Awesome C++][] for a less-biased list of parsers. You can also find other
+single file libraries at [Single file libs][].
 
 </p></details>
 <br/>
 
-None of these libraries fulfill all the above requirements, or really even come close. As you probably have already guessed, CLI11 does.
-So, this library was designed to provide a great syntax, good compiler compatibility, and minimal installation fuss.
+None of these libraries fulfill all the above requirements, or really even come
+close. As you probably have already guessed, CLI11 does. So, this library was
+designed to provide a great syntax, good compiler compatibility, and minimal
+installation fuss.
 
 ### Features not supported by this library
 
-There are some other possible "features" that are intentionally not supported by this library:
-
-* Non-standard variations on syntax, like `-long` options. This is non-standard and should be avoided, so that is enforced by this library.
-* Completion of partial options, such as Python's `argparse` supplies for incomplete arguments. It's better not to guess. Most third party command line parsers for python actually reimplement command line parsing rather than using argparse because of this perceived design flaw (recent versions do have an option to disable it).
-* Autocomplete: This might eventually be added to both Plumbum and CLI11, but it is not supported yet.
-* Wide strings / unicode: Since this uses the standard library only, it might be hard to properly implement, but I would be open to suggestions in how to do this.
+There are some other possible "features" that are intentionally not supported by
+this library:
+
+- Non-standard variations on syntax, like `-long` options. This is non-standard
+  and should be avoided, so that is enforced by this library.
+- Completion of partial options, such as Python's `argparse` supplies for
+  incomplete arguments. It's better not to guess. Most third party command line
+  parsers for python actually reimplement command line parsing rather than using
+  argparse because of this perceived design flaw (recent versions do have an
+  option to disable it).
+- Autocomplete: This might eventually be added to both Plumbum and CLI11, but it
+  is not supported yet.
+- Wide strings / unicode: Since this uses the standard library only, it might be
+  hard to properly implement, but I would be open to suggestions in how to do
+  this.
 
 ## Install
 
 To use, there are several methods:
 
-* All-in-one local header: Copy `CLI11.hpp` from the [most recent release][github releases] into your include directory, and you are set. This is combined from the source files  for every release. This includes the entire command parser library, but does not include separate utilities (like `Timer`, `AutoTimer`). The utilities are completely self contained and can be copied separately.
-* All-in-one global header: Like above, but copying the file to a shared folder location like `/opt/CLI11`. Then, the C++ include path has to be extended to point at this folder. With CMake, use `include_directories(/opt/CLI11)`
-* Local headers and target: Use `CLI/*.hpp` files. You could check out the repository as a git submodule, for example. With CMake, you can use `add_subdirectory` and the `CLI11::CLI11` interface target when linking. If not using a submodule, you must ensure that the copied files are located inside the same tree directory than your current project, to prevent an error with CMake and `add_subdirectory`.
-* Global headers: Use `CLI/*.hpp` files stored in a shared folder. You could check out the git repository to a system-wide folder, for example `/opt/`. With CMake, you could add to the include path via:
+- All-in-one local header: Copy `CLI11.hpp` from the [most recent
+  release][github releases] into your include directory, and you are set. This
+  is combined from the source files for every release. This includes the entire
+  command parser library, but does not include separate utilities (like `Timer`,
+  `AutoTimer`). The utilities are completely self contained and can be copied
+  separately.
+- All-in-one global header: Like above, but copying the file to a shared folder
+  location like `/opt/CLI11`. Then, the C++ include path has to be extended to
+  point at this folder. With CMake, use `include_directories(/opt/CLI11)`
+- Local headers and target: Use `CLI/*.hpp` files. You could check out the
+  repository as a git submodule, for example. With CMake, you can use
+  `add_subdirectory` and the `CLI11::CLI11` interface target when linking. If
+  not using a submodule, you must ensure that the copied files are located
+  inside the same tree directory than your current project, to prevent an error
+  with CMake and `add_subdirectory`.
+- Global headers: Use `CLI/*.hpp` files stored in a shared folder. You could
+  check out the git repository to a system-wide folder, for example `/opt/`.
+  With CMake, you could add to the include path via:
 
 ```bash
 if(NOT DEFINED CLI11_DIR)
@@ -149,7 +198,8 @@ endif()
 include_directories(${CLI11_DIR}/include)
 ```
 
-And then in the source code (adding several headers might be needed to prevent linker errors):
+And then in the source code (adding several headers might be needed to prevent
+linker errors):
 
 ```cpp
 #include "CLI/App.hpp"
@@ -157,23 +207,36 @@ And then in the source code (adding several headers might be needed to prevent l
 #include "CLI/Config.hpp"
 ```
 
-* Global headers and target: configuring and installing the project is required for linking CLI11 to your project in the same way as you would do with any other external library. With CMake, this step allows using `find_package(CLI11 CONFIG REQUIRED)` and then using the `CLI11::CLI11` target when linking. If `CMAKE_INSTALL_PREFIX` was changed during install to a specific folder like `/opt/CLI11`, then you have to pass `-DCLI11_DIR=/opt/CLI11` when building your current project. You can also use [Conan.io][conan-link] or [Hunter][].
-    (These are just conveniences to allow you to use your favorite method of managing packages; it's just header only so including the correct path and
-    using C++11 is all you really need.)
-* Via FetchContent in CMake 3.14+ (or 3.11+ with more work): you can add this with fetch-content, then use the `CLI11::CLI11` target as above, and CMake will download the project in the configure stage:
+- Global headers and target: configuring and installing the project is required
+  for linking CLI11 to your project in the same way as you would do with any
+  other external library. With CMake, this step allows using
+  `find_package(CLI11 CONFIG REQUIRED)` and then using the `CLI11::CLI11` target
+  when linking. If `CMAKE_INSTALL_PREFIX` was changed during install to a
+  specific folder like `/opt/CLI11`, then you have to pass
+  `-DCLI11_DIR=/opt/CLI11` when building your current project. You can also use
+  [Conan.io][conan-link] or [Hunter][]. (These are just conveniences to allow
+  you to use your favorite method of managing packages; it's just header only so
+  including the correct path and using C++11 is all you really need.)
+- Via FetchContent in CMake 3.14+ (or 3.11+ with more work): you can add this
+  with fetch-content, then use the `CLI11::CLI11` target as above, and CMake
+  will download the project in the configure stage:
 
 ```cmake
 include(FetchContent)
 FetchContent_Declare(
   cli11
   GIT_REPOSITORY https://github.com/CLIUtils/CLI11
-  GIT_TAG        v2.1.2
+  GIT_TAG        v2.2.0
 )
 
 FetchContent_MakeAvailable(cli11)
 ```
 
-It is highly recommended that you use the git hash for `GIT_TAG` instead of a tag or branch, as that will both be more secure, as well as faster to reconfigure - CMake will not have to reach out to the internet to see if the tag moved. You can also download just the single header file from the releases using `file(DOWNLOAD`.
+It is highly recommended that you use the git hash for `GIT_TAG` instead of a
+tag or branch, as that will both be more secure, as well as faster to
+reconfigure - CMake will not have to reach out to the internet to see if the tag
+moved. You can also download just the single header file from the releases using
+`file(DOWNLOAD`.
 
 To build the tests, checkout the repository and use CMake:
 
@@ -185,15 +248,24 @@ CTEST_OUTPUT_ON_FAILURE=1 cmake --build build -t test
 
 <details><summary>Note: Special instructions for GCC 8</summary><p>
 
-If you are using GCC 8 and using it in C++17 mode with CLI11.  CLI11 makes use of the `<filesystem>` header if available, but specifically for this compiler, the `filesystem` library is separate from the standard library and needs to be linked separately. So it is available but CLI11 doesn't use it by default.
+If you are using GCC 8 and using it in C++17 mode with CLI11. CLI11 makes use of
+the `<filesystem>` header if available, but specifically for this compiler, the
+`filesystem` library is separate from the standard library and needs to be
+linked separately. So it is available but CLI11 doesn't use it by default.
 
-Specifically `libstdc++fs` needs to be added to the linking list and `CLI11_HAS_FILESYSTEM=1` has to be defined.  Then the filesystem variant of the Validators could be used on GCC 8.  GCC 9+ does not have this issue so the `<filesystem>` is used by default.
+Specifically `libstdc++fs` needs to be added to the linking list and
+`CLI11_HAS_FILESYSTEM=1` has to be defined. Then the filesystem variant of the
+Validators could be used on GCC 8. GCC 9+ does not have this issue so the
+`<filesystem>` is used by default.
 
 There may also be other cases where a specific library needs to be linked.
 
-Defining `CLI11_HAS_FILESYSTEM=0`  which will remove the usage and hence any linking issue.
+Defining `CLI11_HAS_FILESYSTEM=0` which will remove the usage and hence any
+linking issue.
 
-In some cases certain clang compilations may require linking against `libc++fs`.  These situations have not been encountered so the specific situations requiring them are unknown yet.
+In some cases certain clang compilations may require linking against `libc++fs`.
+These situations have not been encountered so the specific situations requiring
+them are unknown yet.
 
 </p></details>
 </br>
@@ -202,7 +274,8 @@ In some cases certain clang compilations may require linking against `libc++fs`.
 
 ### Adding options
 
-To set up, add options, and run, your main function will look something like this:
+To set up, add options, and run, your main function will look something like
+this:
 
 ```cpp
 int main(int argc, char** argv) {
@@ -226,23 +299,33 @@ try {
 }
 ```
 
-The try/catch block ensures that `-h,--help` or a parse error will exit with the correct return code (selected from `CLI::ExitCodes`). (The return here should be inside `main`). You should not assume that the option values have been set inside the catch block; for example, help flags intentionally short-circuit all other processing for speed and to ensure required options and the like do not interfere.
+The try/catch block ensures that `-h,--help` or a parse error will exit with the
+correct return code (selected from `CLI::ExitCodes`). (The return here should be
+inside `main`). You should not assume that the option values have been set
+inside the catch block; for example, help flags intentionally short-circuit all
+other processing for speed and to ensure required options and the like do not
+interfere.
 
 </p></details>
 </br>
 
-The initialization is just one line, adding options is just two each. The parse macro is just one line (or 5 for the contents of the macro).  After the app runs, the filename will be set to the correct value if it was passed, otherwise it will be set to the default. You can check to see if this was passed on the command line with `app.count("--file")`.
+The initialization is just one line, adding options is just two each. The parse
+macro is just one line (or 5 for the contents of the macro). After the app runs,
+the filename will be set to the correct value if it was passed, otherwise it
+will be set to the default. You can check to see if this was passed on the
+command line with `app.count("--file")`.
 
 #### Option types
 
-While all options internally are the same type, there are several ways to add an option depending on what you need. The supported values are:
+While all options internally are the same type, there are several ways to add an
+option depending on what you need. The supported values are:
 
 ```cpp
 // Add options
 app.add_option(option_name, help_str="")
 
 app.add_option(option_name,
-               variable_to_bind_to, // bool, char(see note), int, float, vector, enum, std::atomic, or string-like, or anything with a defined conversion from a string or that takes an int, double, or string in a constructor. Also allowed are tuples, std::array or std::pair. Also supported are complex numbers, wrapper types, and containers besides vectorof any other supported type.
+               variable_to_bind_to, // bool, char(see note), int, float, vector, enum, std::atomic, or string-like, or anything with a defined conversion from a string or that takes an int, double, or string in a constructor. Also allowed are tuples, std::array or std::pair. Also supported are complex numbers, wrapper types, and containers besides vectors of any other supported type.
                help_string="")
 
 app.add_option_function<type>(option_name,
@@ -276,18 +359,32 @@ App* subcom = app.add_subcommand(name, description);
 Option_group *app.add_option_group(name,description);
 ```
 
-An option name may start with any character except ('-', ' ', '\n', and '!') 🆕. For long options, after the first character all characters are allowed except ('=',':','{',' ', '\n')🆕. For the `add_flag*` functions '{' and '!' have special meaning which is why they are not allowed. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on the help line for its positional form.
-
-The `add_option_function<type>(...` function will typically require the template parameter be given unless a `std::function` object with an exact match is passed.  The type can be any type supported by the `add_option` function. The function should throw an error (`CLI::ConversionError` or `CLI::ValidationError` possibly) if the value is not valid.
-
-The two parameter template overload can be used in cases where you want to restrict the input such as
+An option name may start with any character except ('-', ' ', '\n', and '!').
+For long options, after the first character all characters are allowed except
+('=',':','{',' ', '\n'). For the `add_flag*` functions '{' and '!' have special
+meaning which is why they are not allowed. Names are given as a comma separated
+string, with the dash or dashes. An option or flag can have as many names as you
+want, and afterward, using `count`, you can use any of the names, with dashes as
+needed, to count the options. One of the names is allowed to be given without
+proceeding dash(es); if present the option is a positional option, and that name
+will be used on the help line for its positional form.
+
+The `add_option_function<type>(...` function will typically require the template
+parameter be given unless a `std::function` object with an exact match is
+passed. The type can be any type supported by the `add_option` function. The
+function should throw an error (`CLI::ConversionError` or `CLI::ValidationError`
+possibly) if the value is not valid.
+
+The two parameter template overload can be used in cases where you want to
+restrict the input such as
 
 ```cpp
 double val
 app.add_option<double,unsigned int>("-v",val);
 ```
 
-which would first verify the input is convertible to an `unsigned int` before assigning it.  Or using some variant type
+which would first verify the input is convertible to an `unsigned int` before
+assigning it. Or using some variant type
 
 ```cpp
 using vtype=std::variant<int, double, std::string>;
@@ -297,9 +394,19 @@ app.add_option<vtype,int>("--vi",v1);
 app.add_option<vtype,double>("--vf",v1);
 ```
 
-otherwise the output would default to a string.  The `add_option` can be used with any integral or floating point types, enumerations, or strings.  Or any type that takes an int, double, or std\::string in an assignment operator or constructor.  If an object can take multiple varieties of those, std::string takes precedence, then double then int.    To better control which one is used or to use another type for the underlying conversions use the two parameter template to directly specify the conversion type.
+otherwise the output would default to a string. The `add_option` can be used
+with any integral or floating point types, enumerations, or strings. Or any type
+that takes an int, double, or std\::string in an assignment operator or
+constructor. If an object can take multiple varieties of those, std::string
+takes precedence, then double then int. To better control which one is used or
+to use another type for the underlying conversions use the two parameter
+template to directly specify the conversion type.
 
-Types such as (std or boost) `optional<int>`, `optional<double>`, and `optional<string>` and any other wrapper types are supported directly. For purposes of CLI11 wrapper types are those which `value_type` definition.  See [CLI11 Advanced Topics/Custom Converters][] for information on how you can add your own converters for additional types.
+Types such as (std or boost) `optional<int>`, `optional<double>`, and
+`optional<string>` and any other wrapper types are supported directly. For
+purposes of CLI11 wrapper types are those which `value_type` definition. See
+[CLI11 Advanced Topics/Custom Converters][] for information on how you can add
+your own converters for additional types.
 
 Vector types can also be used in the two parameter template overload
 
@@ -308,135 +415,269 @@ std::vector<double> v1;
 app.add_option<std::vector<double>,int>("--vs",v1);
 ```
 
-would load a vector of doubles but ensure all values can be represented as integers.
+would load a vector of doubles but ensure all values can be represented as
+integers.
 
-Automatic direct capture of the default string is disabled when using the two parameter template.  Use `set_default_str(...)` or `->default_function(std::string())` to set the default string or capture function directly for these cases.
+Automatic direct capture of the default string is disabled when using the two
+parameter template. Use `set_default_str(...)` or
+`->default_function(std::string())` to set the default string or capture
+function directly for these cases.
 
-Flag options specified through the `add_flag*` functions allow a syntax for the option names to default particular options to a false value or any other value if some flags are passed.  For example:
+Flag options specified through the `add_flag*` functions allow a syntax for the
+option names to default particular options to a false value or any other value
+if some flags are passed. For example:
 
 ```cpp
 app.add_flag("--flag,!--no-flag",result,"help for flag");
 ```
 
-specifies that if `--flag` is passed on the command line result will be true or contain a value of 1. If `--no-flag` is
-passed `result` will contain false or -1 if `result` is a signed integer type, or 0 if it is an unsigned type.  An
-alternative form of the syntax is more explicit: `"--flag,--no-flag{false}"`; this is equivalent to the previous
-example.  This also works for short form options `"-f,!-n"` or `"-f,-n{false}"`. If `variable_to_bind_to` is anything but an integer value the
-default behavior is to take the last value given, while if `variable_to_bind_to` is an integer type the behavior will be to sum
-all the given arguments and return the result.  This can be modified if needed by changing the `multi_option_policy` on each flag (this is not inherited).
-The default value can be any value. For example if you wished to define a numerical flag:
+specifies that if `--flag` is passed on the command line result will be true or
+contain a value of 1. If `--no-flag` is passed `result` will contain false or -1
+if `result` is a signed integer type, or 0 if it is an unsigned type. An
+alternative form of the syntax is more explicit: `"--flag,--no-flag{false}"`;
+this is equivalent to the previous example. This also works for short form
+options `"-f,!-n"` or `"-f,-n{false}"`. If `variable_to_bind_to` is anything but
+an integer value the default behavior is to take the last value given, while if
+`variable_to_bind_to` is an integer type the behavior will be to sum all the
+given arguments and return the result. This can be modified if needed by
+changing the `multi_option_policy` on each flag (this is not inherited). The
+default value can be any value. For example if you wished to define a numerical
+flag:
 
 ```cpp
 app.add_flag("-1{1},-2{2},-3{3}",result,"numerical flag")
 ```
 
-Using any of those flags on the command line will result in the specified number in the output.  Similar things can be done for string values, and enumerations, as long as the default value can be converted to the given type.
+Using any of those flags on the command line will result in the specified number
+in the output. Similar things can be done for string values, and enumerations,
+as long as the default value can be converted to the given type.
 
-On a `C++14` compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure.
+On a `C++14` compiler, you can pass a callback function directly to `.add_flag`,
+while in C++11 mode you'll need to use `.add_flag_function` if you want a
+callback function. The function will be given the number of times the flag was
+passed. You can throw a relevant `CLI::ParseError` to signal a failure.
 
 #### Example
 
-* `"one,-o,--one"`: Valid as long as not a flag, would create an option that can be specified positionally, or with `-o` or `--one`
-* `"this"` Can only be passed positionally
-* `"-a,-b,-c"` No limit to the number of non-positional option names
+- `"one,-o,--one"`: Valid as long as not a flag, would create an option that can
+  be specified positionally, or with `-o` or `--one`
+- `"this"` Can only be passed positionally
+- `"-a,-b,-c"` No limit to the number of non-positional option names
 
-The add commands return a pointer to an internally stored `Option`.
-This option can be used directly to check for the count (`->count()`) after parsing to avoid a string based lookup.
+The add commands return a pointer to an internally stored `Option`. This option
+can be used directly to check for the count (`->count()`) after parsing to avoid
+a string based lookup.
 
 #### Option options
 
 Before parsing, you can set the following options:
 
-* `->required()`: The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works.
-* `->expected(N)`: Take `N` values instead of as many as possible, only for vector args. If negative, require at least `-N`; end with `--` or another recognized option or subcommand.
-* `->expected(MIN,MAX)`: Set a range of expected values to accompany an option.  `expected(0,1)` is the equivalent of making a flag.
-* `->type_name(typename)`: Set the name of an Option's type (`type_name_fn` allows a function instead)
-* `->type_size(N)`: Set the intrinsic size of an option value. The parser will require multiples of this number if negative. Most of the time this is detected automatically though can be modified for specific use cases.
-* `->type_size(MIN,MAX)`: Set the intrinsic size of an option to a range.
-* `->needs(opt)`: This option requires another option to also be present, opt is an `Option` pointer. Options can be removed from the `needs` with `remove_needs(opt)`. The option can also be specified with a string containing the name of the option
-* `->excludes(opt)`: This option cannot be given with `opt` present, opt is an `Option` pointer.  Can also be given as a string containing the name of the option.  Options can be removed from the excludes list with `->remove_excludes(opt)`
-* `->envname(name)`: Gets the value from the environment if present and not passed on the command line.
-* `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden).
-* `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
-* `->ignore_underscore()`: Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone".  This does not apply to short form options since they only have one character
-* `->disable_flag_override()`: From the command line long form flag options can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line.  The `=` does not work with short form flag options.
-* `->allow_extra_args(true/false)`: If set to true the option will take an unlimited number of arguments like a vector, if false it will limit the number of arguments to the size of the type used in the option.  Default value depends on the nature of the type use, containers default to true, others default to false.
-* `->delimiter(char)`: Allows specification of a custom delimiter for separating single arguments into vector arguments, for example specifying `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3 elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a vector value.
-* `->description(str)`: Set/change the description.
-* `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`,`->take_all()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy). `->join(delim)` can also be used to join with a specific delimiter. This equivalent to calling `->delimiter(delim)` and `->join()`
-* `->check(std::string(const std::string &), validator_name="",validator_description="")`: Define a check function.  The function should return a non empty string with the error message if the check fails
-* `->check(Validator)`: Use a Validator object to do the check see [Validators](#validators) for a description of available Validators and how to create new ones.
-* `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options.
-* `->transform(Validator)`: Uses a Validator object to do the transformation see [Validators](#validators) for a description of available Validators and how to create new ones.
-* `->each(void(const std::string &)>`: Run this function on each value received, as it is received. It should throw a `ValidationError` if an error is encountered.
-* `->configurable(false)`: Disable this option from being in a configuration file.
-* `->capture_default_str()`: Store the current value attached and display it in the help string.
-* `->default_function(std::string())`: Advanced: Change the function that `capture_default_str()` uses.
-* `->always_capture_default()`: Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`.
-* `->default_str(string)`:  Set the default string directly (NO VALIDATION OR CALLBACKS).  This string will also be used as a default value if no arguments are passed and the value is requested.
-* `->default_val(value)`: Generate the default string from a value and validate that the value is also valid.  For options that assign directly to a value type the value in that type is also updated.  Value must be convertible to a string(one of known types or have a stream operator). The callback may be triggered if the `run_callback_for_default` is set.
-* `->run_callback_for_default()`: This will force the option callback to be executed or the variable set when the `default_val` is set.
-* `->option_text(string)`: Sets the text between the option name and description.
-* `->force_callback()`: 🆕 Causes the option callback or value set to be triggered even if the option was not present in parsing.
-* `->trigger_on_parse()`: 🆕 If set, causes the callback and all associated validation checks for the option to be executed when the option value is parsed vs. at the end of all parsing. This could cause the callback to be executed multiple times.
-
-These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. The `each` function takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `each`, `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. Operations added through `transform` are executed first in reverse order of addition, and `check` and `each` are run following the transform functions in order of addition. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results.
+- `->required()`: The program will quit if this option is not present. This is
+  `mandatory` in Plumbum, but required options seems to be a more standard term.
+  For compatibility, `->mandatory()` also works.
+- `->expected(N)`: Take `N` values instead of as many as possible, only for
+  vector args. If negative, require at least `-N`; end with `--` or another
+  recognized option or subcommand.
+- `->expected(MIN,MAX)`: Set a range of expected values to accompany an option.
+  `expected(0,1)` is the equivalent of making a flag.
+- `->type_name(typename)`: Set the name of an Option's type (`type_name_fn`
+  allows a function instead)
+- `->type_size(N)`: Set the intrinsic size of an option value. The parser will
+  require multiples of this number if negative. Most of the time this is
+  detected automatically though can be modified for specific use cases.
+- `->type_size(MIN,MAX)`: Set the intrinsic size of an option to a range.
+- `->needs(opt)`: This option requires another option to also be present, opt is
+  an `Option` pointer. Options can be removed from the `needs` with
+  `remove_needs(opt)`. The option can also be specified with a string containing
+  the name of the option
+- `->excludes(opt)`: This option cannot be given with `opt` present, opt is an
+  `Option` pointer. Can also be given as a string containing the name of the
+  option. Options can be removed from the excludes list with
+  `->remove_excludes(opt)`
+- `->envname(name)`: Gets the value from the environment if present and not
+  passed on the command line.
+- `->group(name)`: The help group to put the option in. No effect for positional
+  options. Defaults to `"Options"`. `""` will not show up in the help print
+  (hidden).
+- `->ignore_case()`: Ignore the case on the command line (also works on
+  subcommands, does not affect arguments).
+- `->ignore_underscore()`: Ignore any underscores in the options names (also
+  works on subcommands, does not affect arguments). For example "option_one"
+  will match with "optionone". This does not apply to short form options since
+  they only have one character
+- `->disable_flag_override()`: From the command line long form flag options can
+  be assigned a value on the command line using the `=` notation `--flag=value`.
+  If this behavior is not desired, the `disable_flag_override()` disables it and
+  will generate an exception if it is done on the command line. The `=` does not
+  work with short form flag options.
+- `->allow_extra_args(true/false)`: If set to true the option will take an
+  unlimited number of arguments like a vector, if false it will limit the number
+  of arguments to the size of the type used in the option. Default value depends
+  on the nature of the type use, containers default to true, others default to
+  false.
+- `->delimiter(char)`: Allows specification of a custom delimiter for separating
+  single arguments into vector arguments, for example specifying
+  `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3
+  elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a
+  vector value.
+- `->description(str)`: Set/change the description.
+- `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option
+  policy. Shortcuts available: `->take_last()`, `->take_first()`,`->take_all()`,
+  and `->join()`. This will only affect options expecting 1 argument or bool
+  flags (which do not inherit their default but always start with a specific
+  policy). `->join(delim)` can also be used to join with a specific delimiter.
+  This equivalent to calling `->delimiter(delim)` and `->join()`. Valid values
+  are `CLI::MultiOptionPolicy::Throw`, `CLI::MultiOptionPolicy::Throw`,
+  `CLI::MultiOptionPolicy::TakeLast`, `CLI::MultiOptionPolicy::TakeFirst`,
+  `CLI::MultiOptionPolicy::Join`, `CLI::MultiOptionPolicy::TakeAll`, and
+  `CLI::MultiOptionPolicy::Sum` 🆕.
+- `->check(std::string(const std::string &), validator_name="",validator_description="")`:
+  Define a check function. The function should return a non empty string with
+  the error message if the check fails
+- `->check(Validator)`: Use a Validator object to do the check see
+  [Validators](#validators) for a description of available Validators and how to
+  create new ones.
+- `->transform(std::string(std::string &), validator_name="",validator_description=")`:
+  Converts the input string into the output string, in-place in the parsed
+  options.
+- `->transform(Validator)`: Uses a Validator object to do the transformation see
+  [Validators](#validators) for a description of available Validators and how to
+  create new ones.
+- `->each(void(const std::string &)>`: Run this function on each value received,
+  as it is received. It should throw a `ValidationError` if an error is
+  encountered.
+- `->configurable(false)`: Disable this option from being in a configuration
+  file.
+- `->capture_default_str()`: Store the current value attached and display it in
+  the help string.
+- `->default_function(std::string())`: Advanced: Change the function that
+  `capture_default_str()` uses.
+- `->always_capture_default()`: Always run `capture_default_str()` when creating
+  new options. Only useful on an App's `option_defaults`.
+- `->default_str(string)`: Set the default string directly (NO VALIDATION OR
+  CALLBACKS). This string will also be used as a default value if no arguments
+  are passed and the value is requested.
+- `->default_val(value)`: Generate the default string from a value and validate
+  that the value is also valid. For options that assign directly to a value type
+  the value in that type is also updated. Value must be convertible to a
+  string(one of known types or have a stream operator). The callback may be
+  triggered if the `run_callback_for_default` is set.
+- `->run_callback_for_default()`: This will force the option callback to be
+  executed or the variable set when the `default_val` is set.
+- `->option_text(string)`: Sets the text between the option name and
+  description.
+- `->force_callback()`: Causes the option callback or value set to be triggered
+  even if the option was not present in parsing.
+- `->trigger_on_parse()`: If set, causes the callback and all associated
+  validation checks for the option to be executed when the option value is
+  parsed vs. at the end of all parsing. This could cause the callback to be
+  executed multiple times. Also works with positional options.
+
+These options return the `Option` pointer, so you can chain them together, and
+even skip storing the pointer entirely. The `each` function takes any function
+that has the signature `void(const std::string&)`; it should throw a
+`ValidationError` when validation fails. The help message will have the name of
+the parent option prepended. Since `each`, `check` and `transform` use the same
+underlying mechanism, you can chain as many as you want, and they will be
+executed in order. Operations added through `transform` are executed first in
+reverse order of addition, and `check` and `each` are run following the
+transform functions in order of addition. If you just want to see the
+unconverted values, use `.results()` to get the `std::vector<std::string>` of
+results.
 
 On the command line, options can be given as:
 
-* `-a` (flag)
-* `-abc` (flags can be combined)
-* `-f filename` (option)
-* `-ffilename` (no space required)
-* `-abcf filename` (flags and option can be combined)
-* `--long` (long flag)
-* `--long_flag=true` (long flag with equals to override default value)
-* `--file filename` (space)
-* `--file=filename` (equals)
-
-If `allow_windows_style_options()` is specified in the application or subcommand options can also be given as:
-
-* `/a` (flag)
-* `/f filename` (option)
-* `/long` (long flag)
-* `/file filename` (space)
-* `/file:filename` (colon)
-* `/long_flag:false` (long flag with : to override the default value)
-  * Windows style options do not allow combining short options or values not separated from the short option like with `-` options
-
-Long flag options may be given with an `=<value>` to allow specifying a false value, or some other value to the flag. See [config files](#configuration-file) for details on the values supported.  NOTE: only the `=` or `:` for windows-style options may be used for this, using a space will result in the argument being interpreted as a positional argument.  This syntax can override the default values, and can be disabled by using `disable_flag_override()`.
-
-Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments.
-If you set `.allow_extras()` on the main `App`, you will not get an error. You can access the missing options using `remaining` (if you have subcommands, `app.remaining(true)` will get all remaining options, subcommands included).
-If the remaining arguments are to processed by another `App` then the function `remaining_for_passthrough()` can be used to get the remaining arguments in reverse order such that `app.parse(vector)` works directly and could even be used inside a subcommand callback.
-
-You can access a vector of pointers to the parsed options in the original order using `parse_order()`.
-If `--` is present in the command line that does not end an unlimited option, then
-everything after that is positional only.
+- `-a` (flag)
+- `-abc` (flags can be combined)
+- `-f filename` (option)
+- `-ffilename` (no space required)
+- `-abcf filename` (flags and option can be combined)
+- `--long` (long flag)
+- `--long_flag=true` (long flag with equals to override default value)
+- `--file filename` (space)
+- `--file=filename` (equals)
+
+If `allow_windows_style_options()` is specified in the application or subcommand
+options can also be given as:
+
+- `/a` (flag)
+- `/f filename` (option)
+- `/long` (long flag)
+- `/file filename` (space)
+- `/file:filename` (colon)
+- `/long_flag:false` (long flag with : to override the default value)
+  - Windows style options do not allow combining short options or values not
+    separated from the short option like with `-` options
+
+Long flag options may be given with an `=<value>` to allow specifying a false
+value, or some other value to the flag. See [config files](#configuration-file)
+for details on the values supported. NOTE: only the `=` or `:` for windows-style
+options may be used for this, using a space will result in the argument being
+interpreted as a positional argument. This syntax can override the default
+values, and can be disabled by using `disable_flag_override()`.
+
+Extra positional arguments will cause the program to exit, so at least one
+positional option with a vector is recommended if you want to allow extraneous
+arguments. If you set `.allow_extras()` on the main `App`, you will not get an
+error. You can access the missing options using `remaining` (if you have
+subcommands, `app.remaining(true)` will get all remaining options, subcommands
+included). If the remaining arguments are to processed by another `App` then the
+function `remaining_for_passthrough()` can be used to get the remaining
+arguments in reverse order such that `app.parse(vector)` works directly and
+could even be used inside a subcommand callback.
+
+You can access a vector of pointers to the parsed options in the original order
+using `parse_order()`. If `--` is present in the command line that does not end
+an unlimited option, then everything after that is positional only.
 
 #### Validators
-Validators are structures to check or modify inputs, they can be used to verify that an input meets certain criteria or transform it into another value.  They are added through the `check` or `transform` functions.  The differences between the two function are that checks do not modify the input whereas transforms can and are executed before any Validators added through `check`.
+
+Validators are structures to check or modify inputs, they can be used to verify
+that an input meets certain criteria or transform it into another value. They
+are added through the `check` or `transform` functions. The differences between
+the two function are that checks do not modify the input whereas transforms can
+and are executed before any Validators added through `check`.
 
 CLI11 has several Validators built-in that perform some common checks
 
-* `CLI::IsMember(...)`: Require an option be a member of a given set. See [Transforming Validators](#transforming-validators) for more details.
-* `CLI::Transformer(...)`: Modify the input using a map. See [Transforming Validators](#transforming-validators) for more details.
-* `CLI::CheckedTransformer(...)`: Modify the input using a map, and require that the input is either in the set or already one of the outputs of the set. See [Transforming Validators](#transforming-validators) for more details.
-* `CLI::AsNumberWithUnit(...)`: Modify the `<NUMBER> <UNIT>` pair by matching the unit and multiplying the number by the corresponding factor. It can be used as a base for transformers, that accept things like size values (`1 KB`) or durations (`0.33 ms`).
-* `CLI::AsSizeValue(...)`: Convert inputs like `100b`, `42 KB`, `101 Mb`, `11 Mib` to absolute values. `KB` can be configured to be interpreted as 10^3 or 2^10.
-* `CLI::ExistingFile`: Requires that the file exists if given.
-* `CLI::ExistingDirectory`: Requires that the directory exists.
-* `CLI::ExistingPath`: Requires that the path (file or directory) exists.
-* `CLI::NonexistentPath`: Requires that the path does not exist.
-* `CLI::Range(min,max)`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0.
-* `CLI::Bounded(min,max)`: Modify the input such that it is always between min and max (make sure to use floating point if needed). Min defaults to 0.  Will produce an error if conversion is not possible.
-* `CLI::PositiveNumber`: Requires the number be greater than 0
-* `CLI::NonNegativeNumber`: Requires the number be greater or equal to 0
-* `CLI::Number`: Requires the input be a number.
-* `CLI::ValidIPV4`: Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`.
-* `CLI::TypeValidator<TYPE>`:Requires that the option be convertible to the specified type e.g.  `CLI::TypeValidator<unsigned int>()` would require that the input be convertible to an `unsigned int` regardless of the end conversion.
-
-These Validators can be used by simply passing the name into the `check` or `transform` methods on an option
+- `CLI::IsMember(...)`: Require an option be a member of a given set. See
+  [Transforming Validators](#transforming-validators) for more details.
+- `CLI::Transformer(...)`: Modify the input using a map. See
+  [Transforming Validators](#transforming-validators) for more details.
+- `CLI::CheckedTransformer(...)`: Modify the input using a map, and require that
+  the input is either in the set or already one of the outputs of the set. See
+  [Transforming Validators](#transforming-validators) for more details.
+- `CLI::AsNumberWithUnit(...)`: Modify the `<NUMBER> <UNIT>` pair by matching
+  the unit and multiplying the number by the corresponding factor. It can be
+  used as a base for transformers, that accept things like size values (`1 KB`)
+  or durations (`0.33 ms`).
+- `CLI::AsSizeValue(...)`: Convert inputs like `100b`, `42 KB`, `101 Mb`,
+  `11 Mib` to absolute values. `KB` can be configured to be interpreted as 10^3
+  or 2^10.
+- `CLI::ExistingFile`: Requires that the file exists if given.
+- `CLI::ExistingDirectory`: Requires that the directory exists.
+- `CLI::ExistingPath`: Requires that the path (file or directory) exists.
+- `CLI::NonexistentPath`: Requires that the path does not exist.
+- `CLI::FileOnDefaultPath`: Best used as a transform, Will check that a file
+  exists either directly or in a default path and update the path appropriately.
+  See [Transforming Validators](#transforming-validators) for more details
+- `CLI::Range(min,max)`: Requires that the option be between min and max (make
+  sure to use floating point if needed). Min defaults to 0.
+- `CLI::Bounded(min,max)`: Modify the input such that it is always between min
+  and max (make sure to use floating point if needed). Min defaults to 0. Will
+  produce an error if conversion is not possible.
+- `CLI::PositiveNumber`: Requires the number be greater than 0
+- `CLI::NonNegativeNumber`: Requires the number be greater or equal to 0
+- `CLI::Number`: Requires the input be a number.
+- `CLI::ValidIPV4`: Requires that the option be a valid IPv4 string e.g.
+  `'255.255.255.255'`, `'10.1.1.7'`.
+- `CLI::TypeValidator<TYPE>`:Requires that the option be convertible to the
+  specified type e.g. `CLI::TypeValidator<unsigned int>()` would require that
+  the input be convertible to an `unsigned int` regardless of the end
+  conversion.
+
+These Validators can be used by simply passing the name into the `check` or
+`transform` methods on an option
 
 ```cpp
 ->check(CLI::ExistingFile);
@@ -459,51 +700,116 @@ will produce a check for a number less than or equal to 0.
 
 ##### Transforming Validators
 
-There are a few built in Validators that let you transform values if used with the `transform` function.  If they also do some checks then they can be used `check` but some may do nothing in that case.
-
-* `CLI::Bounded(min,max)` will bound values between min and max and values outside of that range are limited to min or max,  it will fail if the value cannot be converted and produce a `ValidationError`
-* The `IsMember` Validator lets you specify a set of predefined options. You can pass any container or copyable pointer (including `std::shared_ptr`) to a container to this Validator; the container just needs to be iterable and have a `::value_type`. The key type should be convertible from a string,  You can use an initializer list directly if you like. If you need to modify the set later, the pointer form lets you do that; the type message and check will correctly refer to the current version of the set.  The container passed in can be a set, vector, or a map like structure. If used in the `transform` method the output value will be the matching key as it could be modified by filters.
-
-After specifying a set of options, you can also specify "filter" functions of the form `T(T)`, where `T` is the type of the values. The most common choices probably will be `CLI::ignore_case` an `CLI::ignore_underscore`, and `CLI::ignore_space`.  These all work on strings but it is possible to define functions that work on other types. Here are some examples of `IsMember`:
-
-* `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices.
-* `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too.
-* `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`.
-* `CLI::IsMember(std::map<std::string, TYPE>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched value with the matched key.  The value member of the map is not used in `IsMember`, so it can be any type.
-* `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`: You can modify `p` later.
-* The `Transformer` and `CheckedTransformer` Validators transform one value into another. Any container or copyable pointer (including `std::shared_ptr`) to a container that generates pairs of values can be passed to these `Validator's`; the container just needs to be iterable and have a `::value_type` that consists of pairs. The key type should be convertible from a string, and the value type should be convertible to a string  You can use an initializer list directly if you like. If you need to modify the map later, the pointer form lets you do that; the description message will correctly refer to the current version of the map.  `Transformer` does not do any checking so values not in the map are ignored.  `CheckedTransformer` takes an extra step of verifying that the value is either one of the map key values, in which case it is transformed, or one of the expected output values, and if not will generate a `ValidationError`.  A Transformer placed using `check` will not do anything.
-
-After specifying a map of options, you can also specify "filter" just like in `CLI::IsMember`.
-Here are some examples (`Transformer` and `CheckedTransformer` are interchangeable in the examples)
-of `Transformer`:
-
-* `CLI::Transformer({{"key1", "map1"},{"key2","map2"}})`: Select from key values and produce map values.
-* `CLI::Transformer(std::map<std::string,int>({"two",2},{"three",3},{"four",4}}))`: most maplike containers work,  the `::value_type` needs to produce a pair of some kind.
-* `CLI::CheckedTransformer(std::map<std::string, int>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched key with the value.  `CheckedTransformer` also requires that the value either match one of the keys or match one of known outputs.
-* `auto p = std::make_shared<CLI::TransformPairs<std::string>>(std::initializer_list<std::pair<std::string,std::string>>({"key1", "map1"},{"key2","map2"})); CLI::Transformer(p)`: You can modify `p` later. `TransformPairs<T>` is an alias for `std::vector<std::pair<<std::string,T>>`
-
-NOTES:  If the container used in `IsMember`, `Transformer`, or `CheckedTransformer` has a `find` function like `std::unordered_map`  or `std::map` then that function is used to do the searching. If it does not have a `find` function a linear search is performed.  If there are filters present, the fast search is performed first, and if that fails a linear search with the filters on the key values is performed.
+There are a few built in Validators that let you transform values if used with
+the `transform` function. If they also do some checks then they can be used
+`check` but some may do nothing in that case.
+
+- `CLI::Bounded(min,max)` will bound values between min and max and values
+  outside of that range are limited to min or max, it will fail if the value
+  cannot be converted and produce a `ValidationError`
+- The `IsMember` Validator lets you specify a set of predefined options. You can
+  pass any container or copyable pointer (including `std::shared_ptr`) to a
+  container to this Validator; the container just needs to be iterable and have
+  a `::value_type`. The key type should be convertible from a string, You can
+  use an initializer list directly if you like. If you need to modify the set
+  later, the pointer form lets you do that; the type message and check will
+  correctly refer to the current version of the set. The container passed in can
+  be a set, vector, or a map like structure. If used in the `transform` method
+  the output value will be the matching key as it could be modified by filters.
+
+After specifying a set of options, you can also specify "filter" functions of
+the form `T(T)`, where `T` is the type of the values. The most common choices
+probably will be `CLI::ignore_case` an `CLI::ignore_underscore`, and
+`CLI::ignore_space`. These all work on strings but it is possible to define
+functions that work on other types. Here are some examples of `IsMember`:
+
+- `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices.
+- `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`:
+  Match things like `Choice_1`, too.
+- `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you
+  just need `std::begin`, `std::end`, and `::value_type`.
+- `CLI::IsMember(std::map<std::string, TYPE>({{"one", 1}, {"two", 2}}))`: You
+  can use maps; in `->transform()` these replace the matched value with the
+  matched key. The value member of the map is not used in `IsMember`, so it can
+  be any type.
+- `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`:
+  You can modify `p` later.
+- The `Transformer` and `CheckedTransformer` Validators transform one value into
+  another. Any container or copyable pointer (including `std::shared_ptr`) to a
+  container that generates pairs of values can be passed to these `Validator's`;
+  the container just needs to be iterable and have a `::value_type` that
+  consists of pairs. The key type should be convertible from a string, and the
+  value type should be convertible to a string You can use an initializer list
+  directly if you like. If you need to modify the map later, the pointer form
+  lets you do that; the description message will correctly refer to the current
+  version of the map. `Transformer` does not do any checking so values not in
+  the map are ignored. `CheckedTransformer` takes an extra step of verifying
+  that the value is either one of the map key values, in which case it is
+  transformed, or one of the expected output values, and if not will generate a
+  `ValidationError`. A Transformer placed using `check` will not do anything.
+
+After specifying a map of options, you can also specify "filter" just like in
+`CLI::IsMember`. Here are some examples (`Transformer` and `CheckedTransformer`
+are interchangeable in the examples) of `Transformer`:
+
+- `CLI::Transformer({{"key1", "map1"},{"key2","map2"}})`: Select from key values
+  and produce map values.
+- `CLI::Transformer(std::map<std::string,int>({"two",2},{"three",3},{"four",4}}))`:
+  most maplike containers work, the `::value_type` needs to produce a pair of
+  some kind.
+- `CLI::CheckedTransformer(std::map<std::string, int>({{"one", 1}, {"two", 2}}))`:
+  You can use maps; in `->transform()` these replace the matched key with the
+  value. `CheckedTransformer` also requires that the value either match one of
+  the keys or match one of known outputs.
+- `auto p = std::make_shared<CLI::TransformPairs<std::string>>(std::initializer_list<std::pair<std::string,std::string>>({"key1", "map1"},{"key2","map2"})); CLI::Transformer(p)`:
+  You can modify `p` later. `TransformPairs<T>` is an alias for
+  `std::vector<std::pair<<std::string,T>>`
+
+NOTES: If the container used in `IsMember`, `Transformer`, or
+`CheckedTransformer` has a `find` function like `std::unordered_map` or
+`std::map` then that function is used to do the searching. If it does not have a
+`find` function a linear search is performed. If there are filters present, the
+fast search is performed first, and if that fails a linear search with the
+filters on the key values is performed.
+
+- `CLI::FileOnDefaultPath(default_path)`: 🆕 can be used to check for files in a
+  default path. If used as a transform it will first check that a file exists,
+  if it does nothing further is done, if it does not it tries to add a default
+  Path to the file and search there again. If the file does not exist an error
+  is returned normally but this can be disabled using
+  `CLI::FileOnDefaultPath(default_path, false)`. This allows multiple paths to
+  be chained using multiple transform calls.
 
 ##### Validator operations
 
-Validators are copyable and have a few operations that can be performed on them to alter settings.  Most of the built in Validators have a default description that is displayed in the help.  This can be altered via `.description(validator_description)`.
-The name of a Validator, which is useful for later reference from the `get_validator(name)` method of an `Option` can be set via `.name(validator_name)`
-The operation function of a Validator can be set via
-`.operation(std::function<std::string(std::string &>)`.  The `.active()` function can activate or deactivate a Validator from the operation.  A validator can be set to apply only to a specific element of the output.  For example in a pair option `std::pair<int, std::string>` the first element may need to be a positive integer while the second may need to be a valid file.  The `.application_index(int)` function can specify this.  It is zero based and negative indices apply to all values.
+Validators are copyable and have a few operations that can be performed on them
+to alter settings. Most of the built in Validators have a default description
+that is displayed in the help. This can be altered via
+`.description(validator_description)`. The name of a Validator, which is useful
+for later reference from the `get_validator(name)` method of an `Option` can be
+set via `.name(validator_name)` The operation function of a Validator can be set
+via `.operation(std::function<std::string(std::string &>)`. The `.active()`
+function can activate or deactivate a Validator from the operation. A validator
+can be set to apply only to a specific element of the output. For example in a
+pair option `std::pair<int, std::string>` the first element may need to be a
+positive integer while the second may need to be a valid file. The
+`.application_index(int)` function can specify this. It is zero based and
+negative indices apply to all values.
 
 ```cpp
 opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0));
 opt->check(CLI::Validator(CLI::ExistingFile).application_index(1));
 ```
 
-All the validator operation functions return a Validator reference allowing them to be chained.  For example
+All the validator operation functions return a Validator reference allowing them
+to be chained. For example
 
 ```cpp
 opt->check(CLI::Range(10,20).description("range is limited to sensible values").active(false).name("range"));
 ```
 
-will specify a check on an option with a name "range", but deactivate it for the time being.
-The check can later be activated through
+will specify a check on an option with a name "range", but deactivate it for the
+time being. The check can later be activated through
 
 ```cpp
 opt->get_validator("range")->active();
@@ -523,7 +829,8 @@ or if the operation function is set later they can be created with
 CLI::Validator(validator_description);
 ```
 
- It is also possible to create a subclass of `CLI::Validator`, in which case it can also set a custom description function, and operation function.
+It is also possible to create a subclass of `CLI::Validator`, in which case it
+can also set a custom description function, and operation function.
 
 ##### Querying Validators
 
@@ -533,7 +840,9 @@ Once loaded into an Option, a pointer to a named Validator can be retrieved via
 opt->get_validator(name);
 ```
 
-This will retrieve a Validator with the given name or throw a `CLI::OptionNotFound` error.  If no name is given or name is empty the first unnamed Validator will be returned or the first Validator if there is only one.
+This will retrieve a Validator with the given name or throw a
+`CLI::OptionNotFound` error. If no name is given or name is empty the first
+unnamed Validator will be returned or the first Validator if there is only one.
 
 or
 
@@ -541,107 +850,265 @@ or
 opt->get_validator(index);
 ```
 
-Which will return a validator in the index it is applied which isn't necessarily the order in which was defined.  The pointer can be `nullptr` if an invalid index is given.
-Validators have a few functions to query the current values:
+Which will return a validator in the index it is applied which isn't necessarily
+the order in which was defined. The pointer can be `nullptr` if an invalid index
+is given. Validators have a few functions to query the current values:
 
-* `get_description()`: Will return a description string
-* `get_name()`: Will return the Validator name
-* `get_active()`: Will return the current active state, true if the Validator is active.
-* `get_application_index()`: Will return the current application index.
-* `get_modifying()`: Will return true if the Validator is allowed to modify the input, this can be controlled via the `non_modifying()` method, though it is recommended to let `check` and `transform` option methods manipulate it if needed.
+- `get_description()`: Will return a description string
+- `get_name()`: Will return the Validator name
+- `get_active()`: Will return the current active state, true if the Validator is
+  active.
+- `get_application_index()`: Will return the current application index.
+- `get_modifying()`: Will return true if the Validator is allowed to modify the
+  input, this can be controlled via the `non_modifying()` method, though it is
+  recommended to let `check` and `transform` option methods manipulate it if
+  needed.
 
 #### Getting results
 
-In most cases, the fastest and easiest way is to return the results through a callback or variable specified in one of the `add_*` functions.  But there are situations where this is not possible or desired.  For these cases the results may be obtained through one of the following functions. Please note that these functions will do any type conversions and processing during the call so should not used in performance critical code:
-
-* `->results()`: Retrieves a vector of strings with all the results in the order they were given.
-* `->results(variable_to_bind_to)`: Gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable.
-* `Value=opt->as<type>()`: Returns the result or default value directly as the specified type if possible, can be vector to return all results, and a non-vector to get the result according to the MultiOptionPolicy in place.
+In most cases, the fastest and easiest way is to return the results through a
+callback or variable specified in one of the `add_*` functions. But there are
+situations where this is not possible or desired. For these cases the results
+may be obtained through one of the following functions. Please note that these
+functions will do any type conversions and processing during the call so should
+not used in performance critical code:
+
+- `->results()`: Retrieves a vector of strings with all the results in the order
+  they were given.
+- `->results(variable_to_bind_to)`: Gets the results according to the
+  MultiOptionPolicy and converts them just like the `add_option_function` with a
+  variable.
+- `Value=opt->as<type>()`: Returns the result or default value directly as the
+  specified type if possible, can be vector to return all results, and a
+  non-vector to get the result according to the MultiOptionPolicy in place.
 
 ### Subcommands
 
-Subcommands are supported, and can be nested infinitely. To add a subcommand, call the `add_subcommand` method with a name and an optional description. This gives a pointer to an `App` that behaves just like the main app, and can take options or further subcommands. Add `->ignore_case()` to a subcommand to allow any variation of caps to also be accepted. `->ignore_underscore()` is similar, but for underscores. Children inherit the current setting from the parent. You cannot add multiple matching subcommand names at the same level (including `ignore_case` and `ignore_underscore`).
-
-If you want to require that at least one subcommand is given, use `.require_subcommand()` on the parent app. You can optionally give an exact number of subcommands to require, as well. If you give two arguments, that sets the min and max number allowed.
-0 for the max number allowed will allow an unlimited number of subcommands. As a handy shortcut, a single negative value N will set "up to N" values. Limiting the maximum number allows you to keep arguments that match a previous
-subcommand name from matching.
-
-If an `App` (main or subcommand) has been parsed on the command line, `->parsed` will be true (or convert directly to bool).
-All `App`s have a `get_subcommands()` method, which returns a list of pointers to the subcommands passed on the command line. A `got_subcommand(App_or_name)` method is also provided that will check to see if an `App` pointer or a string name was collected on the command line.
-
-For many cases, however, using an app's callback capabilities may be easier. Every app has a set of callbacks that can be executed at various stages of parsing; a `C++` lambda function (with capture to get parsed values) can be used as input to the callback definition function. If you throw `CLI::Success` or `CLI::RuntimeError(return_value)`, you can
-even exit the program through the callback.
-
-Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved).  The same subcommand can be triggered multiple times but all positional arguments will take precedence over the second and future calls of the subcommand.  `->count()` on the subcommand will return the number of times the subcommand was called.  The subcommand callback will only be triggered once unless the `.immediate_callback()`  flag is set or the callback is specified through the `parse_complete_callback()` function. The `final_callback()` is triggered only once.  In which case the callback executes on completion of the subcommand arguments but after the arguments for that subcommand have been parsed, and can be triggered multiple times.
-
-Subcommands may also have an empty name either by calling `add_subcommand` with an empty string for the name or with no arguments.
-Nameless subcommands function a similarly to groups in the main `App`. See [Option groups](#option-groups) to see how this might work.  If an option is not defined in the main App, all nameless subcommands are checked as well.  This allows for the options to be defined in a composable group.  The `add_subcommand` function has an overload for adding a `shared_ptr<App>` so the subcommand(s) could be defined in different components and merged into a main `App`, or possibly multiple `Apps`.  Multiple nameless subcommands are allowed.  Callbacks for nameless subcommands are only triggered if any options from the subcommand were parsed. Subcommand names given through the `add_subcommand` method have the same restrictions as option names.
+Subcommands are supported, and can be nested infinitely. To add a subcommand,
+call the `add_subcommand` method with a name and an optional description. This
+gives a pointer to an `App` that behaves just like the main app, and can take
+options or further subcommands. Add `->ignore_case()` to a subcommand to allow
+any variation of caps to also be accepted. `->ignore_underscore()` is similar,
+but for underscores. Children inherit the current setting from the parent. You
+cannot add multiple matching subcommand names at the same level (including
+`ignore_case` and `ignore_underscore`).
+
+If you want to require that at least one subcommand is given, use
+`.require_subcommand()` on the parent app. You can optionally give an exact
+number of subcommands to require, as well. If you give two arguments, that sets
+the min and max number allowed. 0 for the max number allowed will allow an
+unlimited number of subcommands. As a handy shortcut, a single negative value N
+will set "up to N" values. Limiting the maximum number allows you to keep
+arguments that match a previous subcommand name from matching.
+
+If an `App` (main or subcommand) has been parsed on the command line, `->parsed`
+will be true (or convert directly to bool). All `App`s have a
+`get_subcommands()` method, which returns a list of pointers to the subcommands
+passed on the command line. A `got_subcommand(App_or_name)` method is also
+provided that will check to see if an `App` pointer or a string name was
+collected on the command line.
+
+For many cases, however, using an app's callback capabilities may be easier.
+Every app has a set of callbacks that can be executed at various stages of
+parsing; a `C++` lambda function (with capture to get parsed values) can be used
+as input to the callback definition function. If you throw `CLI::Success` or
+`CLI::RuntimeError(return_value)`, you can even exit the program through the
+callback.
+
+Multiple subcommands are allowed, to allow [`Click`][click] like series of
+commands (order is preserved). The same subcommand can be triggered multiple
+times but all positional arguments will take precedence over the second and
+future calls of the subcommand. `->count()` on the subcommand will return the
+number of times the subcommand was called. The subcommand callback will only be
+triggered once unless the `.immediate_callback()` flag is set or the callback is
+specified through the `parse_complete_callback()` function. The
+`final_callback()` is triggered only once. In which case the callback executes
+on completion of the subcommand arguments but after the arguments for that
+subcommand have been parsed, and can be triggered multiple times.
+
+Subcommands may also have an empty name either by calling `add_subcommand` with
+an empty string for the name or with no arguments. Nameless subcommands function
+a similarly to groups in the main `App`. See [Option groups](#option-groups) to
+see how this might work. If an option is not defined in the main App, all
+nameless subcommands are checked as well. This allows for the options to be
+defined in a composable group. The `add_subcommand` function has an overload for
+adding a `shared_ptr<App>` so the subcommand(s) could be defined in different
+components and merged into a main `App`, or possibly multiple `Apps`. Multiple
+nameless subcommands are allowed. Callbacks for nameless subcommands are only
+triggered if any options from the subcommand were parsed. Subcommand names given
+through the `add_subcommand` method have the same restrictions as option names.
 
 #### Subcommand options
 
-There are several options that are supported on the main app and subcommands and option_groups. These are:
-
-* `.ignore_case()`: Ignore the case of this subcommand. Inherited by added subcommands, so is usually used on the main `App`.
-* `.ignore_underscore()`: Ignore any underscores in the subcommand name. Inherited by added subcommands, so is usually used on the main `App`.
-* `.allow_windows_style_options()`: Allow command line options to be parsed in the form of `/s /long /file:file_name.ext`  This option does not change how options are specified in the `add_option` calls or the ability to process options in the form of `-s --long --file=file_name.ext`.
-* `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent option. Subcommands always are allowed to "fall through" as in they will first attempt to match on the current subcommand and if they fail will progressively check parents for matching subcommands.
-* `.configurable()`: Allow the subcommand to be triggered from a configuration file. By default subcommand options in a configuration file do not trigger a subcommand but will just update default values.
-* `.disable()`: Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group.
-* `.disabled_by_default()`: Specify that at the start of parsing the subcommand/option_group should be disabled. This is useful for allowing some Subcommands to trigger others.
-* `.enabled_by_default()`: Specify that at the start of each parse the subcommand/option_group should be enabled.  This is useful for allowing some Subcommands to disable others.
-* `.silent()`: Specify that the subcommand is silent meaning that if used it won't show up in the subcommand list.  This allows the use of subcommands as modifiers
-* `.validate_positionals()`: Specify that positionals should pass validation before matching.  Validation is specified through `transform`, `check`, and `each` for an option.  If an argument fails validation it is not an error and matching proceeds to the next available positional or extra arguments.
-* `.excludes(option_or_subcommand)`: If given an option pointer or pointer to another subcommand, these subcommands cannot be given together.  In the case of options, if the option is passed the subcommand cannot be used and will generate an error.
-* `.needs(option_or_subcommand)`: If given an option pointer or pointer to another subcommand, the subcommands will require the given option to have been given before this subcommand is validated which occurs prior to execution of any callback or after parsing is completed.
-* `.require_option()`: Require 1 or more options or option groups be used.
-* `.require_option(N)`: Require `N` options or option groups, if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more.
-* `.require_option(min, max)`: Explicitly set min and max allowed options or option groups. Setting `max` to 0 implies unlimited options.
-* `.require_subcommand()`: Require 1 or more subcommands.
-* `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more.
-* `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited.
-* `.add_subcommand(name="", description="")`: Add a subcommand, returns a pointer to the internally stored subcommand.
-* `.add_subcommand(shared_ptr<App>)`: Add a subcommand by shared_ptr, returns a pointer to the internally stored subcommand.
-* `.remove_subcommand(App)`: Remove a subcommand from the app or subcommand.
-* `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line.
-* `.get_subcommands(filter)`: The list of subcommands that match a particular filter function.
-* `.add_option_group(name="", description="")`: Add an [option group](#option-groups) to an App,  an option group is specialized subcommand intended for containing groups of options or other groups for controlling how options interact.
-* `.get_parent()`: Get the parent App or `nullptr` if called on main App.
-* `.get_option(name)`: Get an option pointer by option name will throw if the specified option is not available,  nameless subcommands are also searched
-* `.get_option_no_throw(name)`: Get an option pointer by option name. This function will return a `nullptr` instead of throwing if the option is not available.
-* `.get_options(filter)`: Get the list of all defined option pointers (useful for processing the app for custom output formats).
-* `.parse_order()`: Get the list of option pointers in the order they were parsed (including duplicates).
-* `.formatter(fmt)`: Set a formatter, with signature `std::string(const App*, std::string, AppFormatMode)`. See Formatting for more details.
-* `.description(str)`: Set/change the description.
-* `.get_description()`: Access the description.
-* `.alias(str)`: set an alias for the subcommand, this allows subcommands to be called by more than one name.
-* `.parsed()`: True if this subcommand was given on the command line.
-* `.count()`: Returns the number of times the subcommand was called.
-* `.count(option_name)`: Returns the number of times a particular option was called.
-* `.count_all()`: Returns the total number of arguments a particular subcommand processed, on the main App it returns the total number of processed commands.
-* `.name(name)`: Add or change the name.
-* `.callback(void() function)`: Set the callback for an app. Either sets the `pre_parse_callback` or the `final_callback` depending on the value of `immediate_callback`. See [Subcommand callbacks](#callbacks) for some additional details.
-* `.parse_complete_callback(void() function)`: Set the callback that runs at the completion of parsing. For subcommands this is executed at the completion of the single subcommand and can be executed multiple times. See [Subcommand callbacks](#callbacks) for some additional details.
-* `.final_callback(void() function)`: Set the callback that runs at the end of all processing. This is the last thing that is executed before returning. See [Subcommand callbacks](#callbacks) for some additional details.
-* `.immediate_callback()`: Specifies whether the callback for a subcommand should be run as a `parse_complete_callback`(true) or `final_callback`(false). When used on the main app it will execute the main app callback prior to the callbacks for a subcommand if they do not also have the `immediate_callback` flag set. It is preferable to use the `parse_complete_callback` or `final_callback` directly instead of the `callback` and `immediate_callback` if one wishes to control the ordering and timing of callback.  Though `immediate_callback` can be used to swap them if that is needed.
-* `.pre_parse_callback(void(std::size_t) function)`: Set a callback that executes after the first argument of an application is processed.  See [Subcommand callbacks](#callbacks) for some additional details.
-* `.allow_extras()`: Do not throw an error if extra arguments are left over.
-* `.positionals_at_end()`: Specify that positional arguments occur as the last arguments and throw an error if an unexpected positional is encountered.
-* `.prefix_command()`: Like `allow_extras`, but stop immediately on the first unrecognized item. It is ideal for allowing your app or subcommand to be a "prefix" to calling another app.
-* `.footer(message)`: Set text to appear at the bottom of the help string.
-* `.footer(std::string())`: Set a callback to generate a string that will appear at the end of the help string.
-* `.set_help_flag(name, message)`: Set the help flag name and message, returns a pointer to the created option.
-* `.set_version_flag(name, versionString or callback, help_message)`: Set the version flag name and version string or callback and optional help message, returns a pointer to the created option.
-* `.set_help_all_flag(name, message)`: Set the help all flag name and message, returns a pointer to the created option. Expands subcommands.
-* `.failure_message(func)`: Set the failure message function. Two provided: `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default).
-* `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting `""` will be hide the subcommand.
-* `[option_name]`: retrieve a const pointer to an option given by `option_name` for Example `app["--flag1"]` will get a pointer to the option for the "--flag1" value,  `app["--flag1"]->as<bool>()` will get the results of the command line for a flag. The operation will throw an exception if the option name is not valid.
-
-> Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function, and any positional argument will match before repeated subcommand names.
+There are several options that are supported on the main app and subcommands and
+option_groups. These are:
+
+- `.ignore_case()`: Ignore the case of this subcommand. Inherited by added
+  subcommands, so is usually used on the main `App`.
+- `.ignore_underscore()`: Ignore any underscores in the subcommand name.
+  Inherited by added subcommands, so is usually used on the main `App`.
+- `.allow_windows_style_options()`: Allow command line options to be parsed in
+  the form of `/s /long /file:file_name.ext` This option does not change how
+  options are specified in the `add_option` calls or the ability to process
+  options in the form of `-s --long --file=file_name.ext`.
+- `.fallthrough()`: Allow extra unmatched options and positionals to "fall
+  through" and be matched on a parent option. Subcommands always are allowed to
+  "fall through" as in they will first attempt to match on the current
+  subcommand and if they fail will progressively check parents for matching
+  subcommands.
+- `.configurable()`: Allow the subcommand to be triggered from a configuration
+  file. By default subcommand options in a configuration file do not trigger a
+  subcommand but will just update default values.
+- `.disable()`: Specify that the subcommand is disabled, if given with a bool
+  value it will enable or disable the subcommand or option group.
+- `.disabled_by_default()`: Specify that at the start of parsing the
+  subcommand/option_group should be disabled. This is useful for allowing some
+  Subcommands to trigger others.
+- `.enabled_by_default()`: Specify that at the start of each parse the
+  subcommand/option_group should be enabled. This is useful for allowing some
+  Subcommands to disable others.
+- `.silent()`: Specify that the subcommand is silent meaning that if used it
+  won't show up in the subcommand list. This allows the use of subcommands as
+  modifiers
+- `.validate_positionals()`: Specify that positionals should pass validation
+  before matching. Validation is specified through `transform`, `check`, and
+  `each` for an option. If an argument fails validation it is not an error and
+  matching proceeds to the next available positional or extra arguments.
+- `.validate_optional_arguments()`:🆕 Specify that optional arguments should
+  pass validation before being assigned to an option. Validation is specified
+  through `transform`, `check`, and `each` for an option. If an argument fails
+  validation it is not an error and matching proceeds to the next available
+  positional subcommand or extra arguments.
+- `.excludes(option_or_subcommand)`: If given an option pointer or pointer to
+  another subcommand, these subcommands cannot be given together. In the case of
+  options, if the option is passed the subcommand cannot be used and will
+  generate an error.
+- `.needs(option_or_subcommand)`: If given an option pointer or pointer to
+  another subcommand, the subcommands will require the given option to have been
+  given before this subcommand is validated which occurs prior to execution of
+  any callback or after parsing is completed.
+- `.require_option()`: Require 1 or more options or option groups be used.
+- `.require_option(N)`: Require `N` options or option groups, if `N>0`, or up to
+  `N` if `N<0`. `N=0` resets to the default to 0 or more.
+- `.require_option(min, max)`: Explicitly set min and max allowed options or
+  option groups. Setting `max` to 0 implies unlimited options.
+- `.require_subcommand()`: Require 1 or more subcommands.
+- `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if
+  `N<0`. `N=0` resets to the default to 0 or more.
+- `.require_subcommand(min, max)`: Explicitly set min and max allowed
+  subcommands. Setting `max` to 0 is unlimited.
+- `.add_subcommand(name="", description="")`: Add a subcommand, returns a
+  pointer to the internally stored subcommand.
+- `.add_subcommand(shared_ptr<App>)`: Add a subcommand by shared_ptr, returns a
+  pointer to the internally stored subcommand.
+- `.remove_subcommand(App)`: Remove a subcommand from the app or subcommand.
+- `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on
+  the command line.
+- `.get_subcommands(filter)`: The list of subcommands that match a particular
+  filter function.
+- `.add_option_group(name="", description="")`: Add an
+  [option group](#option-groups) to an App, an option group is specialized
+  subcommand intended for containing groups of options or other groups for
+  controlling how options interact.
+- `.get_parent()`: Get the parent App or `nullptr` if called on main App.
+- `.get_option(name)`: Get an option pointer by option name will throw if the
+  specified option is not available, nameless subcommands are also searched
+- `.get_option_no_throw(name)`: Get an option pointer by option name. This
+  function will return a `nullptr` instead of throwing if the option is not
+  available.
+- `.get_options(filter)`: Get the list of all defined option pointers (useful
+  for processing the app for custom output formats).
+- `.parse_order()`: Get the list of option pointers in the order they were
+  parsed (including duplicates).
+- `.formatter(fmt)`: Set a formatter, with signature
+  `std::string(const App*, std::string, AppFormatMode)`. See Formatting for more
+  details.
+- `.description(str)`: Set/change the description.
+- `.get_description()`: Access the description.
+- `.alias(str)`: set an alias for the subcommand, this allows subcommands to be
+  called by more than one name.
+- `.parsed()`: True if this subcommand was given on the command line.
+- `.count()`: Returns the number of times the subcommand was called.
+- `.count(option_name)`: Returns the number of times a particular option was
+  called.
+- `.count_all()`: Returns the total number of arguments a particular subcommand
+  processed, on the main App it returns the total number of processed commands.
+- `.name(name)`: Add or change the name.
+- `.callback(void() function)`: Set the callback for an app. Either sets the
+  `pre_parse_callback` or the `final_callback` depending on the value of
+  `immediate_callback`. See [Subcommand callbacks](#callbacks) for some
+  additional details.
+- `.parse_complete_callback(void() function)`: Set the callback that runs at the
+  completion of parsing. For subcommands this is executed at the completion of
+  the single subcommand and can be executed multiple times. See
+  [Subcommand callbacks](#callbacks) for some additional details.
+- `.final_callback(void() function)`: Set the callback that runs at the end of
+  all processing. This is the last thing that is executed before returning. See
+  [Subcommand callbacks](#callbacks) for some additional details.
+- `.immediate_callback()`: Specifies whether the callback for a subcommand
+  should be run as a `parse_complete_callback`(true) or `final_callback`(false).
+  When used on the main app it will execute the main app callback prior to the
+  callbacks for a subcommand if they do not also have the `immediate_callback`
+  flag set. It is preferable to use the `parse_complete_callback` or
+  `final_callback` directly instead of the `callback` and `immediate_callback`
+  if one wishes to control the ordering and timing of callback. Though
+  `immediate_callback` can be used to swap them if that is needed.
+- `.pre_parse_callback(void(std::size_t) function)`: Set a callback that
+  executes after the first argument of an application is processed. See
+  [Subcommand callbacks](#callbacks) for some additional details.
+- `.allow_extras()`: Do not throw an error if extra arguments are left over.
+- `.positionals_at_end()`: Specify that positional arguments occur as the last
+  arguments and throw an error if an unexpected positional is encountered.
+- `.prefix_command()`: Like `allow_extras`, but stop immediately on the first
+  unrecognized item. It is ideal for allowing your app or subcommand to be a
+  "prefix" to calling another app.
+- `.footer(message)`: Set text to appear at the bottom of the help string.
+- `.footer(std::string())`: Set a callback to generate a string that will appear
+  at the end of the help string.
+- `.set_help_flag(name, message)`: Set the help flag name and message, returns a
+  pointer to the created option.
+- `.set_version_flag(name, versionString or callback, help_message)`: Set the
+  version flag name and version string or callback and optional help message,
+  returns a pointer to the created option.
+- `.set_help_all_flag(name, message)`: Set the help all flag name and message,
+  returns a pointer to the created option. Expands subcommands.
+- `.failure_message(func)`: Set the failure message function. Two provided:
+  `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default).
+- `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting `""`
+  will be hide the subcommand.
+- `[option_name]`: retrieve a const pointer to an option given by `option_name`
+  for Example `app["--flag1"]` will get a pointer to the option for the
+  "--flag1" value, `app["--flag1"]->as<bool>()` will get the results of the
+  command line for a flag. The operation will throw an exception if the option
+  name is not valid.
+
+> Note: if you have a fixed number of required positional options, that will
+> match before subcommand names. `{}` is an empty filter function, and any
+> positional argument will match before repeated subcommand names.
 
 #### Callbacks
 
-A subcommand has three optional callbacks that are executed at different stages of processing.  The `preparse_callback` is executed once after the first argument of a subcommand or application is processed and gives an argument for the number of remaining arguments to process.  For the main app the first argument is considered the program name,  for subcommands the first argument is the subcommand name.  For Option groups and nameless subcommands the first argument is after the first argument or subcommand is processed from that group.
-The second callback is executed after parsing.  This is known as the `parse_complete_callback`. For subcommands this is executed immediately after parsing and can be executed multiple times if a subcommand is called multiple times.    On the main app this callback is executed after all the `parse_complete_callback`s for the subcommands are executed but prior to any `final_callback` calls in the subcommand or option groups. If the main app or subcommand has a config file, no data from the config file will be reflected in `parse_complete_callback` on named subcommands.  For `option_group`s the `parse_complete_callback` is executed prior to the `parse_complete_callback` on the main app but after the `config_file` is loaded (if specified).  The `final_callback` is executed after all processing is complete.  After the `parse_complete_callback` is executed on the main app, the used subcommand `final_callback` are executed followed by the "final callback" for option groups.  The last thing to execute is the `final_callback` for the `main_app`.
+A subcommand has three optional callbacks that are executed at different stages
+of processing. The `preparse_callback` is executed once after the first argument
+of a subcommand or application is processed and gives an argument for the number
+of remaining arguments to process. For the main app the first argument is
+considered the program name, for subcommands the first argument is the
+subcommand name. For Option groups and nameless subcommands the first argument
+is after the first argument or subcommand is processed from that group. The
+second callback is executed after parsing. This is known as the
+`parse_complete_callback`. For subcommands this is executed immediately after
+parsing and can be executed multiple times if a subcommand is called multiple
+times. On the main app this callback is executed after all the
+`parse_complete_callback`s for the subcommands are executed but prior to any
+`final_callback` calls in the subcommand or option groups. If the main app or
+subcommand has a config file, no data from the config file will be reflected in
+`parse_complete_callback` on named subcommands. For `option_group`s the
+`parse_complete_callback` is executed prior to the `parse_complete_callback` on
+the main app but after the `config_file` is loaded (if specified). The
+`final_callback` is executed after all processing is complete. After the
+`parse_complete_callback` is executed on the main app, the used subcommand
+`final_callback` are executed followed by the "final callback" for option
+groups. The last thing to execute is the `final_callback` for the `main_app`.
 For example say an application was set up like
 
 ```cpp
@@ -660,23 +1127,31 @@ Then the command line is given as
 program --opt1 opt1_val  sub1 --sub1opt --sub1optb val sub2 --sub2opt sub1 --sub1opt2 sub2 --sub2opt2 val
 ```
 
-* `pa` will be called prior to parsing any values with an argument of 13.
-* `pc1` will be called immediately after processing the `sub1` command with a value of 10.
-* `c1` will be called when the `sub2` command is encountered.
-* `pc2` will be called with value of 6 after the `sub2` command is encountered.
-* `c1` will be called again after the second `sub2` command is encountered.
-* `ac1` will be called after processing of all arguments
-* `c2` will be called once after processing all arguments.
-* `ac2` will be called last after completing  all lower level callbacks have been executed.
+- `pa` will be called prior to parsing any values with an argument of 13.
+- `pc1` will be called immediately after processing the `sub1` command with a
+  value of 10.
+- `c1` will be called when the `sub2` command is encountered.
+- `pc2` will be called with value of 6 after the `sub2` command is encountered.
+- `c1` will be called again after the second `sub2` command is encountered.
+- `ac1` will be called after processing of all arguments
+- `c2` will be called once after processing all arguments.
+- `ac2` will be called last after completing all lower level callbacks have been
+  executed.
 
-A subcommand is considered terminated when one of the following conditions are met.
+A subcommand is considered terminated when one of the following conditions are
+met.
 
 1. There are no more arguments to process
-2. Another subcommand is encountered that would not fit in an optional slot of the subcommand
-3. The `positional_mark` (`--`) is encountered and there are no available positional slots in the subcommand.
+2. Another subcommand is encountered that would not fit in an optional slot of
+   the subcommand
+3. The `positional_mark` (`--`) is encountered and there are no available
+   positional slots in the subcommand.
 4. The `subcommand_terminator` mark (`++`) is encountered
 
-Prior to executed a `parse_complete_callback` all contained options are processed before the callback is triggered.  If a subcommand with a `parse_complete_callback` is called again, then the contained options are reset, and can be triggered again.
+Prior to executed a `parse_complete_callback` all contained options are
+processed before the callback is triggered. If a subcommand with a
+`parse_complete_callback` is called again, then the contained options are reset,
+and can be triggered again.
 
 #### Option groups
 
@@ -686,7 +1161,18 @@ The subcommand method
 .add_option_group(name,description)
 ```
 
-Will create an option group, and return a pointer to it. The argument for `description` is optional and can be omitted.  An option group allows creation of a collection of options, similar to the groups function on options, but with additional controls and requirements.  They allow specific sets of options to be composed and controlled as a collective.  For an example see [range example](https://github.com/CLIUtils/CLI11/blob/main/examples/ranges.cpp).  Option groups are a specialization of an App so all [functions](#subcommand-options) that work with an App or subcommand also work on option groups.  Options can be created as part of an option group using the add functions just like a subcommand, or previously created options can be added through.  The name given in an option group must not contain newlines or null characters.🆕
+Will create an option group, and return a pointer to it. The argument for
+`description` is optional and can be omitted. An option group allows creation of
+a collection of options, similar to the groups function on options, but with
+additional controls and requirements. They allow specific sets of options to be
+composed and controlled as a collective. For an example see
+[range example](https://github.com/CLIUtils/CLI11/blob/main/examples/ranges.cpp).
+Option groups are a specialization of an App so all
+[functions](#subcommand-options) that work with an App or subcommand also work
+on option groups. Options can be created as part of an option group using the
+add functions just like a subcommand, or previously created options can be added
+through. The name given in an option group must not contain newlines or null
+characters.
 
 ```cpp
 ogroup->add_option(option_pointer);
@@ -694,49 +1180,75 @@ ogroup->add_options(option_pointer);
 ogroup->add_options(option1,option2,option3,...);
 ```
 
-The option pointers used in this function must be options defined in the parent application of the option group otherwise an error will be generated.  Subcommands can also be added via
+The option pointers used in this function must be options defined in the parent
+application of the option group otherwise an error will be generated.
+Subcommands can also be added via
 
 ```cpp
 ogroup->add_subcommand(subcom_pointer);
 ```
 
-This results in the subcommand being moved from its parent into the option group.
-
-Options in an option group are searched for a command line match after any options in the main app, so any positionals in the main app would be matched first.  So care must be taken to make sure of the order when using positional arguments and option groups.
-Option groups work well with `excludes` and `require_options` methods, as an application will treat an option group as a single option for the purpose of counting and requirements, and an option group will be considered used if any of the options or subcommands contained in it are used.  Option groups allow specifying requirements such as requiring 1 of 3 options in one group and 1 of 3 options in a different group. Option groups can contain other groups as well.   Disabling an option group will turn off all options within the group.
-
-The `CLI::TriggerOn` and `CLI::TriggerOff` methods are helper functions to allow the use of options/subcommands from one group to trigger another group on or off.
+This results in the subcommand being moved from its parent into the option
+group.
+
+Options in an option group are searched for a command line match after any
+options in the main app, so any positionals in the main app would be matched
+first. So care must be taken to make sure of the order when using positional
+arguments and option groups. Option groups work well with `excludes` and
+`require_options` methods, as an application will treat an option group as a
+single option for the purpose of counting and requirements, and an option group
+will be considered used if any of the options or subcommands contained in it are
+used. Option groups allow specifying requirements such as requiring 1 of 3
+options in one group and 1 of 3 options in a different group. Option groups can
+contain other groups as well. Disabling an option group will turn off all
+options within the group.
+
+The `CLI::TriggerOn` and `CLI::TriggerOff` methods are helper functions to allow
+the use of options/subcommands from one group to trigger another group on or
+off.
 
 ```cpp
 CLI::TriggerOn(group1_pointer, triggered_group);
 CLI::TriggerOff(group2_pointer, disabled_group);
 ```
 
-These functions make use of `preparse_callback`, `enabled_by_default()` and `disabled_by_default`.  The triggered group may be a vector of group pointers.  These methods should only be used once per group and will override any previous use of the underlying functions.  More complex arrangements can be accomplished using similar methodology with a custom `preparse_callback` function that does more.
+These functions make use of `preparse_callback`, `enabled_by_default()` and
+`disabled_by_default`. The triggered group may be a vector of group pointers.
+These methods should only be used once per group and will override any previous
+use of the underlying functions. More complex arrangements can be accomplished
+using similar methodology with a custom `preparse_callback` function that does
+more.
 
-Additional helper functions `deprecate_option` and `retire_option` are available to deprecate or retire options
+Additional helper functions `deprecate_option` and `retire_option` are available
+to deprecate or retire options
 
 ```cpp
 CLI::deprecate_option(option *, replacement_name="");
 CLI::deprecate_option(App,option_name,replacement_name="");
 ```
 
-will specify that the option is deprecated which will display a message in the help and a warning on first usage.  Deprecated options function normally but will add a message in the help and display a warning on first use.
+will specify that the option is deprecated which will display a message in the
+help and a warning on first usage. Deprecated options function normally but will
+add a message in the help and display a warning on first use.
 
 ```cpp
 CLI::retire_option(App,option *);
 CLI::retire_option(App,option_name);
 ```
 
-will create an option that does nothing by default and will display a warning on first usage that the option is retired and has no effect.  If the option exists it is replaces with a dummy option that takes the same arguments.
+will create an option that does nothing by default and will display a warning on
+first usage that the option is retired and has no effect. If the option exists
+it is replaces with a dummy option that takes the same arguments.
 
-If an empty string is passed the option group name the entire group will be hidden in the help results.  For example.
+If an empty string is passed the option group name the entire group will be
+hidden in the help results. For example.
 
 ```cpp
 auto hidden_group=app.add_option_group("");
 ```
 
-will create a group such that no options in that group are displayed in the help string.
+will create a group such that no options in that group are displayed in the help
+string.
 
 ### Configuration file
 
@@ -747,7 +1259,16 @@ app.set_config(option_name="",
                required=false)
 ```
 
-If this is called with no arguments, it will remove the configuration file option (like `set_help_flag`). Setting a configuration option is special. If it is present, it will be read along with the normal command line arguments. The file will be read if it exists, and does not throw an error unless `required` is `true`. Configuration files are in [TOML][] format by default, though the default reader can also accept files in INI format as well. It should be noted that CLI11 does not contain a full TOML parser but can read strings from most TOML file and run them through the CLI11 parser. Other formats can be added by an adept user, some variations are available through customization points in the default formatter. An example of a TOML file:
+If this is called with no arguments, it will remove the configuration file
+option (like `set_help_flag`). Setting a configuration option is special. If it
+is present, it will be read along with the normal command line arguments. The
+file will be read if it exists, and does not throw an error unless `required` is
+`true`. Configuration files are in [TOML][] format by default, though the
+default reader can also accept files in INI format as well. It should be noted
+that CLI11 does not contain a full TOML parser but can read strings from most
+TOML file and run them through the CLI11 parser. Other formats can be added by
+an adept user, some variations are available through customization points in the
+default formatter. An example of a TOML file:
 
 ```toml
 # Comments are supported, using a #
@@ -781,24 +1302,66 @@ in_subcommand = Wow
 sub.subcommand = true
 ```
 
-Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`, `enable`; or `false`, `off`, `0`, `no`, `disable` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not necessarily mean that subcommand was passed, it just sets the "defaults"). You cannot set positional-only arguments.  Subcommands can be triggered from configuration files if the `configurable` flag was set on the subcommand.  Then the use of `[subcommand]` notation will trigger a subcommand and cause it to act as if it were on the command line.
+Spaces before and after the name and argument are ignored. Multiple arguments
+are separated by spaces. One set of quotes will be removed, preserving spaces
+(the same way the command line works). Boolean options can be `true`, `on`, `1`,
+`yes`, `enable`; or `false`, `off`, `0`, `no`, `disable` (case insensitive).
+Sections (and `.` separated names) are treated as subcommands (note: this does
+not necessarily mean that subcommand was passed, it just sets the "defaults").
+You cannot set positional-only arguments. Subcommands can be triggered from
+configuration files if the `configurable` flag was set on the subcommand. Then
+the use of `[subcommand]` notation will trigger a subcommand and cause it to act
+as if it were on the command line.
+
+To print a configuration file from the passed arguments, use
+`.config_to_str(default_also=false, write_description=false)`, where
+`default_also` will also show any defaulted arguments, and `write_description`
+will include the app and option descriptions. See
+[Config files](https://cliutils.github.io/CLI11/book/chapters/config.html) for
+some additional details and customization points.
+
+If it is desired that multiple configuration be allowed. Use
 
-To print a configuration file from the passed
-arguments, use `.config_to_str(default_also=false, write_description=false)`, where `default_also` will also show any defaulted arguments, and `write_description` will include the app and option descriptions.  See [Config files](https://cliutils.github.io/CLI11/book/chapters/config.html) for some additional details and customization points.
+```cpp
+app.set_config("--config")->expected(1, X);
+```
+
+Where X is some positive number and will allow up to `X` configuration files to
+be specified by separate `--config` arguments. Value strings with quote
+characters in it will be printed with a single quote. All other arguments will
+use double quote. Empty strings will use a double quoted argument. Numerical or
+boolean values are not quoted.
 
-If it is desired that multiple configuration be allowed.  Use
+For options or flags which allow 0 arguments to be passed using an empty string
+in the config file, `{}`, or `[]` will convert the result to the default value
+specified via `default_str` or `default_val` on the option 🆕. If no user
+specified default is given the result is an empty string or the converted value
+of an empty string.
+
+NOTE: Transforms and checks can be used with the option pointer returned from
+set_config like any other option to validate the input if needed. It can also be
+used with the built in transform `CLI::FileOnDefaultPath` to look in a default
+path as well as the current one. For example
 
 ```cpp
-app.set_config("--config")->expected(1, X);
+app.set_config("--config")->transform(CLI::FileOnDefaultPath("/to/default/path/"));
 ```
 
-Where X is some positive number and will allow up to `X` configuration files to be specified by separate `--config` arguments.  Value strings with quote characters in it will be printed with a single quote. All other arguments will use double quote.  Empty strings will use a double quoted argument. Numerical or boolean values are not quoted.
+See [Transforming Validators](#transforming-validators) for additional details
+on this validator. Multiple transforms or validators can be used either by
+multiple calls or using `|` operations with the transform.
 
 ### Inheriting defaults
 
-Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `ignore_underscore`, `fallthrough`, `group`, `footer`,`immediate_callback` and maximum number of required subcommands. The help flag existence, name, and description are inherited, as well.
+Many of the defaults for subcommands and even options are inherited from their
+creators. The inherited default values for subcommands are `allow_extras`,
+`prefix_command`, `ignore_case`, `ignore_underscore`, `fallthrough`, `group`,
+`footer`,`immediate_callback` and maximum number of required subcommands. The
+help flag existence, name, and description are inherited, as well.
 
-Options have defaults for `group`, `required`, `multi_option_policy`, `ignore_case`, `ignore_underscore`, `delimiter`, and `disable_flag_override`. To set these defaults, you should set the `option_defaults()` object, for example:
+Options have defaults for `group`, `required`, `multi_option_policy`,
+`ignore_case`, `ignore_underscore`, `delimiter`, and `disable_flag_override`. To
+set these defaults, you should set the `option_defaults()` object, for example:
 
 ```cpp
 app.option_defaults()->required();
@@ -809,32 +1372,73 @@ The default settings for options are inherited to subcommands, as well.
 
 ### Formatting
 
-The job of formatting help printouts is delegated to a formatter callable object on Apps and Options. You are free to replace either formatter by calling `formatter(fmt)` on an `App`, where fmt is any copyable callable with the correct signature.
-CLI11 comes with a default App formatter functional, `Formatter`. It is customizable; you can set `label(key, value)` to replace the default labels like `REQUIRED`, and `column_width(n)` to set the width of the columns before you add the functional to the app or option. You can also override almost any stage of the formatting process in a subclass of either formatter. If you want to make a new formatter from scratch, you can do
-that too; you just need to implement the correct signature. The first argument is a const pointer to the in question. The formatter will get a `std::string` usage name as the second option, and a `AppFormatMode` mode for the final option. It should return a `std::string`.
-
-The `AppFormatMode` can be `Normal`, `All`, or `Sub`, and it indicates the situation the help was called in. `Sub` is optional, but the default formatter uses it to make sure expanded subcommands are called with
-their own formatter since you can't access anything but the call operator once a formatter has been set.
+The job of formatting help printouts is delegated to a formatter callable object
+on Apps and Options. You are free to replace either formatter by calling
+`formatter(fmt)` on an `App`, where fmt is any copyable callable with the
+correct signature. CLI11 comes with a default App formatter functional,
+`Formatter`. It is customizable; you can set `label(key, value)` to replace the
+default labels like `REQUIRED`, and `column_width(n)` to set the width of the
+columns before you add the functional to the app or option. You can also
+override almost any stage of the formatting process in a subclass of either
+formatter. If you want to make a new formatter from scratch, you can do that
+too; you just need to implement the correct signature. The first argument is a
+const pointer to the in question. The formatter will get a `std::string` usage
+name as the second option, and a `AppFormatMode` mode for the final option. It
+should return a `std::string`.
+
+The `AppFormatMode` can be `Normal`, `All`, or `Sub`, and it indicates the
+situation the help was called in. `Sub` is optional, but the default formatter
+uses it to make sure expanded subcommands are called with their own formatter
+since you can't access anything but the call operator once a formatter has been
+set.
 
 ### Subclassing
 
-The App class was designed allow toolkits to subclass it, to provide preset default options (see above) and setup/teardown code. Subcommands remain an unsubclassed `App`, since those are not expected to need setup and teardown. The default `App` only adds a help flag, `-h,--help`, than can removed/replaced using `.set_help_flag(name, help_string)`. You can also set a help-all flag with `.set_help_all_flag(name, help_string)`; this will expand the subcommands (one level only). You can remove options if you have pointers to them using `.remove_option(opt)`. You can add a `pre_callback` override to customize the after parse
-but before run behavior, while
-still giving the user freedom to `callback` on the main app.
-
-The most important parse function is `parse(std::vector<std::string>)`, which takes a reversed list of arguments (so that `pop_back` processes the args in the correct order). `get_help_ptr` and `get_config_ptr` give you access to the help/config option pointers. The standard `parse` manually sets the name from the first argument, so it should not be in this vector. You can also use `parse(string, bool)` to split up and parse a single string; the optional boolean should be set to true if you are
-including the program name in the string, and false otherwise.  The program name can contain spaces if it is an existing file,  otherwise can be enclosed in quotes(single quote, double quote or backtick).  Embedded quote characters can be escaped with `\`.
-
-Also, in a related note, the `App` you get a pointer to is stored in the parent `App` in a `shared_ptr`s (similar to `Option`s) and are deleted when the main `App` goes out of scope unless the object has another owner.
+The App class was designed allow toolkits to subclass it, to provide preset
+default options (see above) and setup/teardown code. Subcommands remain an
+unsubclassed `App`, since those are not expected to need setup and teardown. The
+default `App` only adds a help flag, `-h,--help`, than can removed/replaced
+using `.set_help_flag(name, help_string)`. You can also set a help-all flag with
+`.set_help_all_flag(name, help_string)`; this will expand the subcommands (one
+level only). You can remove options if you have pointers to them using
+`.remove_option(opt)`. You can add a `pre_callback` override to customize the
+after parse but before run behavior, while still giving the user freedom to
+`callback` on the main app.
+
+The most important parse function is `parse(std::vector<std::string>)`, which
+takes a reversed list of arguments (so that `pop_back` processes the args in the
+correct order). `get_help_ptr` and `get_config_ptr` give you access to the
+help/config option pointers. The standard `parse` manually sets the name from
+the first argument, so it should not be in this vector. You can also use
+`parse(string, bool)` to split up and parse a single string; the optional
+boolean should be set to true if you are including the program name in the
+string, and false otherwise. The program name can contain spaces if it is an
+existing file, otherwise can be enclosed in quotes(single quote, double quote or
+backtick). Embedded quote characters can be escaped with `\`.
+
+Also, in a related note, the `App` you get a pointer to is stored in the parent
+`App` in a `shared_ptr`s (similar to `Option`s) and are deleted when the main
+`App` goes out of scope unless the object has another owner.
 
 ### How it works
 
-Every `add_` option you have seen so far depends on one method that takes a lambda function. Each of these methods is just making a different lambda function with capture to populate the option. The function has full access to the vector of strings, so it knows how many times an option was passed or how many arguments it received. The lambda returns `true` if it could validate the option strings, and
-`false` if it failed.
+Every `add_` option you have seen so far depends on one method that takes a
+lambda function. Each of these methods is just making a different lambda
+function with capture to populate the option. The function has full access to
+the vector of strings, so it knows how many times an option was passed or how
+many arguments it received. The lambda returns `true` if it could validate the
+option strings, and `false` if it failed.
 
-Other values can be added as long as they support `operator>>` (and defaults can be printed if they support `operator<<`). To add a new type, for example, provide a custom `operator>>` with an `istream` (inside the CLI namespace is fine if you don't want to interfere with an existing `operator>>`).
+Other values can be added as long as they support `operator>>` (and defaults can
+be printed if they support `operator<<`). To add a new type, for example,
+provide a custom `operator>>` with an `istream` (inside the CLI namespace is
+fine if you don't want to interfere with an existing `operator>>`).
 
-If you wanted to extend this to support a completely new type, use a lambda or add a specialization of the `lexical_cast` function template in the namespace of the type you need to convert to. Some examples of some new parsers for `complex<double>` that support all of the features of a standard `add_options` call are in [one of the tests](./tests/NewParseTest.cpp). A simpler example is shown below:
+If you wanted to extend this to support a completely new type, use a lambda or
+add an overload of the `lexical_cast` function in the namespace of the type you
+need to convert to. Some examples of some new parsers for `complex<double>` that
+support all of the features of a standard `add_options` call are in
+[one of the tests](./tests/NewParseTest.cpp). A simpler example is shown below:
 
 #### Example
 
@@ -847,7 +1451,10 @@ app.add_option("--fancy-count", [](std::vector<std::string> val){
 
 ### Utilities
 
-There are a few other utilities that are often useful in CLI programming. These are in separate headers, and do not appear in `CLI11.hpp`, but are completely independent and can be used as needed. The `Timer`/`AutoTimer` class allows you to easily time a block of code, with custom print output.
+There are a few other utilities that are often useful in CLI programming. These
+are in separate headers, and do not appear in `CLI11.hpp`, but are completely
+independent and can be used as needed. The `Timer`/`AutoTimer` class allows you
+to easily time a block of code, with custom print output.
 
 ```cpp
 {
@@ -856,12 +1463,18 @@ some_long_running_process();
 }
 ```
 
-This will create a timer with a title (default: `Timer`), and will customize the output using the predefined `Big` output (default: `Simple`). Because it is an `AutoTimer`, it will print out the time elapsed when the timer is destroyed at the end of the block. If you use `Timer` instead, you can use `to_string` or `std::cout << timer << std::endl;` to print the time. The print function can be any function that takes two strings, the title and the time, and returns a formatted
-string for printing.
+This will create a timer with a title (default: `Timer`), and will customize the
+output using the predefined `Big` output (default: `Simple`). Because it is an
+`AutoTimer`, it will print out the time elapsed when the timer is destroyed at
+the end of the block. If you use `Timer` instead, you can use `to_string` or
+`std::cout << timer << std::endl;` to print the time. The print function can be
+any function that takes two strings, the title and the time, and returns a
+formatted string for printing.
 
 ### Other libraries
 
-If you use the excellent [Rang][] library to add color to your terminal in a safe, multi-platform way, you can combine it with CLI11 nicely:
+If you use the excellent [Rang][] library to add color to your terminal in a
+safe, multi-platform way, you can combine it with CLI11 nicely:
 
 ```cpp
 std::atexit([](){std::cout << rang::style::reset;});
@@ -873,9 +1486,11 @@ try {
 }
 ```
 
-This will print help in blue, errors in red, and will reset before returning the terminal to the user.
+This will print help in blue, errors in red, and will reset before returning the
+terminal to the user.
 
-If you are on a Unix-like system, and you'd like to handle control-c and color, you can add:
+If you are on a Unix-like system, and you'd like to handle control-c and color,
+you can add:
 
 ```cpp
  #include <csignal>
@@ -899,41 +1514,88 @@ And, in your main function:
 
 ## API
 
-The API is [documented here][api-docs]. Also see the [CLI11 tutorial GitBook][gitbook].
+The API is [documented here][api-docs]. Also see the [CLI11 tutorial
+GitBook][gitbook].
 
 ## Examples
 
-Several short examples of different features are included in the repository. A brief description of each is included here
-
-* [callback_passthrough](https://github.com/CLIUtils/CLI11/blob/main/examples/callback_passthrough.cpp): Example of directly passing remaining arguments through to a callback function which generates a CLI11 application based on existing arguments.
-* [custom_parse](https://github.com/CLIUtils/CLI11/blob/main/examples/custom_parse.cpp): Based on [Issue #566](https://github.com/CLIUtils/CLI11/issues/566), example of custom parser
-* [digit_args](https://github.com/CLIUtils/CLI11/blob/main/examples/digit_args.cpp): Based on [Issue #123](https://github.com/CLIUtils/CLI11/issues/123), uses digit flags to pass a value
-* [enum](https://github.com/CLIUtils/CLI11/blob/main/examples/enum.cpp): Using enumerations in an option, and the use of [CheckedTransformer](#transforming-validators)
-* [enum_ostream](https://github.com/CLIUtils/CLI11/blob/main/examples/enum_ostream.cpp): In addition to the contents of example enum.cpp, this example shows how a custom ostream operator overrides CLI11's enum streaming.
-* [formatter](https://github.com/CLIUtils/CLI11/blob/main/examples/formatter.cpp): Illustrating usage of a custom formatter
-* [groups](https://github.com/CLIUtils/CLI11/blob/main/examples/groups.cpp): Example using groups of options for help grouping and a the timer helper class
-* [inter_argument_order](https://github.com/CLIUtils/CLI11/blob/main/examples/inter_argument_order.cpp): An app to practice mixing unlimited arguments, but still recover the original order.
-* [json](https://github.com/CLIUtils/CLI11/blob/main/examples/json.cpp): Using JSON as a config file parser
-* [modhelp](https://github.com/CLIUtils/CLI11/blob/main/examples/modhelp.cpp): How to modify the help flag to do something other than default
-* [nested](https://github.com/CLIUtils/CLI11/blob/main/examples/nested.cpp): Nested subcommands
-* [option_groups](https://github.com/CLIUtils/CLI11/blob/main/examples/option_groups.cpp): Illustrating the use of option groups and a required number of options. Based on [Issue #88](https://github.com/CLIUtils/CLI11/issues/88) to set interacting groups of options
-* [positional_arity](https://github.com/CLIUtils/CLI11/blob/main/examples/positional_arity.cpp): Illustrating use of `preparse_callback` to handle situations where the number of arguments can determine which should get parsed,  Based on [Issue #166](https://github.com/CLIUtils/CLI11/issues/166)
-* [positional_validation](https://github.com/CLIUtils/CLI11/blob/main/examples/positional_validation.cpp): Example of how positional arguments are validated using the `validate_positional` flag, also based on [Issue #166](https://github.com/CLIUtils/CLI11/issues/166)
-* [prefix_command](https://github.com/CLIUtils/CLI11/blob/main/examples/prefix_command.cpp): Illustrating use of the `prefix_command` flag.
-* [ranges](https://github.com/CLIUtils/CLI11/blob/main/examples/ranges.cpp): App to demonstrate exclusionary option groups based on [Issue #88](https://github.com/CLIUtils/CLI11/issues/88)
-* [shapes](https://github.com/CLIUtils/CLI11/blob/main/examples/shapes.cpp): Illustrating how to set up repeated subcommands Based on [gitter discussion](https://gitter.im/CLI11gitter/Lobby?at=5c7af6b965ffa019ea788cd5)
-* [simple](https://github.com/CLIUtils/CLI11/blob/main/examples/simple.cpp): A simple example of how to set up a CLI11 Application with different flags and options
-* [subcom_help](https://github.com/CLIUtils/CLI11/blob/main/examples/subcom_help.cpp): Configuring help for subcommands
-* [subcom_partitioned](https://github.com/CLIUtils/CLI11/blob/main/examples/subcom_partitioned.cpp): Example with a timer and subcommands generated separately and added to the main app later.
-* [subcommands](https://github.com/CLIUtils/CLI11/blob/main/examples/subcommands.cpp): Short example of subcommands
-* [validators](https://github.com/CLIUtils/CLI11/blob/main/examples/validators.cpp): Example illustrating use of validators
+Several short examples of different features are included in the repository. A
+brief description of each is included here
+
+- [callback_passthrough](https://github.com/CLIUtils/CLI11/blob/main/examples/callback_passthrough.cpp):
+  Example of directly passing remaining arguments through to a callback function
+  which generates a CLI11 application based on existing arguments.
+- [custom_parse](https://github.com/CLIUtils/CLI11/blob/main/examples/custom_parse.cpp):
+  Based on [Issue #566](https://github.com/CLIUtils/CLI11/issues/566), example
+  of custom parser
+- [digit_args](https://github.com/CLIUtils/CLI11/blob/main/examples/digit_args.cpp):
+  Based on [Issue #123](https://github.com/CLIUtils/CLI11/issues/123), uses
+  digit flags to pass a value
+- [enum](https://github.com/CLIUtils/CLI11/blob/main/examples/enum.cpp): Using
+  enumerations in an option, and the use of
+  [CheckedTransformer](#transforming-validators)
+- [enum_ostream](https://github.com/CLIUtils/CLI11/blob/main/examples/enum_ostream.cpp):
+  In addition to the contents of example enum.cpp, this example shows how a
+  custom ostream operator overrides CLI11's enum streaming.
+- [formatter](https://github.com/CLIUtils/CLI11/blob/main/examples/formatter.cpp):
+  Illustrating usage of a custom formatter
+- [groups](https://github.com/CLIUtils/CLI11/blob/main/examples/groups.cpp):
+  Example using groups of options for help grouping and a the timer helper class
+- [inter_argument_order](https://github.com/CLIUtils/CLI11/blob/main/examples/inter_argument_order.cpp):
+  An app to practice mixing unlimited arguments, but still recover the original
+  order.
+- [json](https://github.com/CLIUtils/CLI11/blob/main/examples/json.cpp): Using
+  JSON as a config file parser
+- [modhelp](https://github.com/CLIUtils/CLI11/blob/main/examples/modhelp.cpp):
+  How to modify the help flag to do something other than default
+- [nested](https://github.com/CLIUtils/CLI11/blob/main/examples/nested.cpp):
+  Nested subcommands
+- [option_groups](https://github.com/CLIUtils/CLI11/blob/main/examples/option_groups.cpp):
+  Illustrating the use of option groups and a required number of options. Based
+  on [Issue #88](https://github.com/CLIUtils/CLI11/issues/88) to set interacting
+  groups of options
+- [positional_arity](https://github.com/CLIUtils/CLI11/blob/main/examples/positional_arity.cpp):
+  Illustrating use of `preparse_callback` to handle situations where the number
+  of arguments can determine which should get parsed, Based on
+  [Issue #166](https://github.com/CLIUtils/CLI11/issues/166)
+- [positional_validation](https://github.com/CLIUtils/CLI11/blob/main/examples/positional_validation.cpp):
+  Example of how positional arguments are validated using the
+  `validate_positional` flag, also based on
+  [Issue #166](https://github.com/CLIUtils/CLI11/issues/166)
+- [prefix_command](https://github.com/CLIUtils/CLI11/blob/main/examples/prefix_command.cpp):
+  Illustrating use of the `prefix_command` flag.
+- [ranges](https://github.com/CLIUtils/CLI11/blob/main/examples/ranges.cpp): App
+  to demonstrate exclusionary option groups based on
+  [Issue #88](https://github.com/CLIUtils/CLI11/issues/88)
+- [shapes](https://github.com/CLIUtils/CLI11/blob/main/examples/shapes.cpp):
+  Illustrating how to set up repeated subcommands Based on
+  [gitter discussion](https://gitter.im/CLI11gitter/Lobby?at=5c7af6b965ffa019ea788cd5)
+- [simple](https://github.com/CLIUtils/CLI11/blob/main/examples/simple.cpp): A
+  simple example of how to set up a CLI11 Application with different flags and
+  options
+- [subcom_help](https://github.com/CLIUtils/CLI11/blob/main/examples/subcom_help.cpp):
+  Configuring help for subcommands
+- [subcom_partitioned](https://github.com/CLIUtils/CLI11/blob/main/examples/subcom_partitioned.cpp):
+  Example with a timer and subcommands generated separately and added to the
+  main app later.
+- [subcommands](https://github.com/CLIUtils/CLI11/blob/main/examples/subcommands.cpp):
+  Short example of subcommands
+- [validators](https://github.com/CLIUtils/CLI11/blob/main/examples/validators.cpp):
+  Example illustrating use of validators
 
 ## Contribute
 
-To contribute, open an [issue][github issues] or [pull request][github pull requests] on GitHub, or ask a question on [gitter][]. There is also a short note to contributors [here](./.github/CONTRIBUTING.md).
-This readme roughly follows the [Standard Readme Style][] and includes a mention of almost every feature of the library. More complex features are documented in more detail in the [CLI11 tutorial GitBook][gitbook].
+To contribute, open an [issue][github issues] or [pull
+request][github pull requests] on GitHub, or ask a question on [gitter][]. There
+is also a short note to contributors [here](./.github/CONTRIBUTING.md). This
+readme roughly follows the [Standard Readme Style][] and includes a mention of
+almost every feature of the library. More complex features are documented in
+more detail in the [CLI11 tutorial GitBook][gitbook].
 
-This project was created by [Henry Schreiner](https://github.com/henryiii) and major features were added by  [Philip Top](https://github.com/phlptp). Special thanks to all the contributors ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
+This project was created by [Henry Schreiner](https://github.com/henryiii) and
+major features were added by [Philip Top](https://github.com/phlptp). Special
+thanks to all the contributors
+([emoji key](https://allcontributors.org/docs/en/emoji-key)):
 
 <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
 <!-- prettier-ignore-start -->
@@ -1009,27 +1671,38 @@ This project was created by [Henry Schreiner](https://github.com/henryiii) and m
 
 <!-- markdownlint-enable -->
 <!-- prettier-ignore-end -->
+
 <!-- ALL-CONTRIBUTORS-LIST:END -->
 
-This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
+This project follows the
+[all-contributors](https://github.com/all-contributors/all-contributors)
+specification. Contributions of any kind welcome!
 
 ## License
 
-As of version 1.0, this library is available under a 3-Clause BSD license. See the [LICENSE](./LICENSE) file for details.
+As of version 1.0, this library is available under a 3-Clause BSD license. See
+the [LICENSE](./LICENSE) file for details.
 
-CLI11 was developed at the [University of Cincinnati][] to support of the [GooFit][] library under [NSF Award 1414736][]. Version 0.9 was featured in a [DIANA/HEP][] meeting at CERN ([see the slides][diana slides]). Please give it a try! Feedback is always welcome.
+CLI11 was developed at the [University of Cincinnati][] to support of the
+[GooFit][] library under [NSF Award 1414736][]. Version 0.9 was featured in a
+[DIANA/HEP][] meeting at CERN ([see the slides][diana slides]). Please give it a
+try! Feedback is always welcome.
 
 [doi-badge]: https://zenodo.org/badge/80064252.svg
 [doi-link]: https://zenodo.org/badge/latestdoi/80064252
-[azure-badge]: https://dev.azure.com/CLIUtils/CLI11/_apis/build/status/CLIUtils.CLI11?branchName=main
+[azure-badge]:
+  https://dev.azure.com/CLIUtils/CLI11/_apis/build/status/CLIUtils.CLI11?branchName=main
 [azure]: https://dev.azure.com/CLIUtils/CLI11
 [actions-link]: https://github.com/CLIUtils/CLI11/actions
-[actions-badge]: https://github.com/CLIUtils/CLI11/actions/workflows/tests.yml/badge.svg
-[appveyor-badge]: https://ci.appveyor.com/api/projects/status/82niaxpaa28dwbms/branch/main?svg=true
+[actions-badge]:
+  https://github.com/CLIUtils/CLI11/actions/workflows/tests.yml/badge.svg
+[appveyor-badge]:
+  https://ci.appveyor.com/api/projects/status/82niaxpaa28dwbms/branch/main?svg=true
 [appveyor]: https://ci.appveyor.com/project/HenrySchreiner/cli11
 [repology-badge]: https://repology.org/badge/latest-versions/cli11.svg
 [repology]: https://repology.org/project/cli11/versions
-[codecov-badge]: https://codecov.io/gh/CLIUtils/CLI11/branch/main/graph/badge.svg?token=2O4wfs8NJO
+[codecov-badge]:
+  https://codecov.io/gh/CLIUtils/CLI11/branch/main/graph/badge.svg?token=2O4wfs8NJO
 [codecov]: https://codecov.io/gh/CLIUtils/CLI11
 [gitter-badge]: https://badges.gitter.im/CLI11gitter/Lobby.svg
 [gitter]: https://gitter.im/CLI11gitter/Lobby
@@ -1046,7 +1719,8 @@ CLI11 was developed at the [University of Cincinnati][] to support of the [GooFi
 [click]: http://click.pocoo.org
 [api-docs]: https://CLIUtils.github.io/CLI11/index.html
 [rang]: https://github.com/agauniyal/rang
-[boost program options]: http://www.boost.org/doc/libs/1_63_0/doc/html/program_options.html
+[boost program options]:
+  http://www.boost.org/doc/libs/1_63_0/doc/html/program_options.html
 [the lean mean c++ option parser]: http://optionparser.sourceforge.net
 [tclap]: http://tclap.sourceforge.net
 [cxxopts]: https://github.com/jarro2783/cxxopts
@@ -1057,7 +1731,8 @@ CLI11 was developed at the [University of Cincinnati][] to support of the [GooFi
 [nsf award 1414736]: https://nsf.gov/awardsearch/showAward?AWD_ID=1414736
 [university of cincinnati]: http://www.uc.edu
 [gitbook]: https://cliutils.github.io/CLI11/book/
-[cli11 advanced topics/custom converters]: https://cliutils.gitlab.io/CLI11Tutorial/chapters/advanced-topics.html#custom-converters
+[cli11 advanced topics/custom converters]:
+  https://cliutils.gitlab.io/CLI11Tutorial/chapters/advanced-topics.html#custom-converters
 [programoptions.hxx]: https://github.com/Fytch/ProgramOptions.hxx
 [argument aggregator]: https://github.com/vietjtnguyen/argagg
 [args]: https://github.com/Taywee/args
@@ -1072,14 +1747,20 @@ CLI11 was developed at the [University of Cincinnati][] to support of the [GooFi
 [wandbox-badge]: https://img.shields.io/badge/try_2.1-online-blue.svg
 [wandbox-link]: https://wandbox.org/permlink/CA5bymNHh0AczdeN
 [releases-badge]: https://img.shields.io/github/release/CLIUtils/CLI11.svg
-[cli11-po-compare]: https://iscinumpy.gitlab.io/post/comparing-cli11-and-boostpo/
-[diana slides]: https://indico.cern.ch/event/619465/contributions/2507949/attachments/1448567/2232649/20170424-diana-2.pdf
+[cli11-po-compare]:
+  https://iscinumpy.gitlab.io/post/comparing-cli11-and-boostpo/
+[diana slides]:
+  https://indico.cern.ch/event/619465/contributions/2507949/attachments/1448567/2232649/20170424-diana-2.pdf
 [awesome c++]: https://github.com/fffaraz/awesome-cpp/blob/master/README.md#cli
 [cli]: https://codesynthesis.com/projects/cli/
-[single file libs]: https://github.com/nothings/single_file_libs/blob/master/README.md
-[codacy-badge]: https://app.codacy.com/project/badge/Grade/2796b969c1b54321a02ad08affec0800
-[codacy-link]: https://www.codacy.com/gh/CLIUtils/CLI11/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=CLIUtils/CLI11&amp;utm_campaign=Badge_Grade
+[single file libs]:
+  https://github.com/nothings/single_file_libs/blob/master/README.md
+[codacy-badge]:
+  https://app.codacy.com/project/badge/Grade/2796b969c1b54321a02ad08affec0800
+[codacy-link]:
+  https://www.codacy.com/gh/CLIUtils/CLI11/dashboard?utm_source=github.com&utm_medium=referral&utm_content=CLIUtils/CLI11&utm_campaign=Badge_Grade
 [hunter]: https://docs.hunter.sh/en/latest/packages/pkg/CLI11.html
 [standard readme style]: https://github.com/RichardLitt/standard-readme
 [argparse]: https://github.com/p-ranav/argparse
 [toml]: https://toml.io
+[lyra]: https://github.com/bfgroup/Lyra
diff --git a/cmake/CLI11Warnings.cmake b/cmake/CLI11Warnings.cmake
new file mode 100644
index 0000000..8b4b499
--- /dev/null
+++ b/cmake/CLI11Warnings.cmake
@@ -0,0 +1,37 @@
+# Special target that adds warnings. Is not exported.
+add_library(CLI11_warnings INTERFACE)
+
+set(unix-warnings -Wall -Wextra -pedantic -Wshadow -Wsign-conversion -Wswitch-enum)
+
+# Clang warnings
+# -Wfloat-equal could be added with Catch::literals and _a usage
+if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+  list(
+    APPEND
+    unix-warnings
+    -Wcast-align
+    -Wimplicit-atomic-properties
+    -Wmissing-declarations
+    -Woverlength-strings
+    -Wshadow
+    -Wstrict-selector-match
+    -Wundeclared-selector)
+  # -Wunreachable-code Doesn't work on Clang 3.4
+endif()
+
+# Buggy in GCC 4.8
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9)
+  list(APPEND unix-warnings -Weffc++)
+endif()
+
+target_compile_options(
+  CLI11_warnings
+  INTERFACE $<$<BOOL:${CLI11_FORCE_LIBCXX}>:-stdlib=libc++>
+            $<$<CXX_COMPILER_ID:MSVC>:/W4
+            $<$<BOOL:${CLI11_WARNINGS_AS_ERRORS}>:/WX>>
+            $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:${unix-warnings}
+            $<$<BOOL:${CLI11_WARNINGS_AS_ERRORS}>:-Werror>>)
+
+if(NOT CMAKE_VERSION VERSION_LESS 3.13)
+  target_link_options(CLI11_warnings INTERFACE $<$<BOOL:${CLI11_FORCE_LIBCXX}>:-stdlib=libc++>)
+endif()
diff --git a/debian/changelog b/debian/changelog
index 9e71b45..3547e51 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+cli11 (2.3.2+ds-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Wed, 05 Apr 2023 10:53:24 -0000
+
 cli11 (2.1.2+ds-1) unstable; urgency=medium
 
   * New upstream version 2.1.2+ds
diff --git a/debian/patches/0001_use_system_catch2.patch b/debian/patches/0001_use_system_catch2.patch
index 7cba5c7..29a6bae 100644
--- a/debian/patches/0001_use_system_catch2.patch
+++ b/debian/patches/0001_use_system_catch2.patch
@@ -3,11 +3,11 @@ Author: Jose Luis Rivero <jrivero@osrfoundation.org>
 Last-Update: 2021-08-11
 Forwarded: No
 
-diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
-index 80c4f6a..685591e 100644
---- a/tests/CMakeLists.txt
-+++ b/tests/CMakeLists.txt
-@@ -63,33 +63,9 @@ endif()
+Index: cli11.git/tests/CMakeLists.txt
+===================================================================
+--- cli11.git.orig/tests/CMakeLists.txt
++++ cli11.git/tests/CMakeLists.txt
+@@ -61,33 +61,9 @@ endif()
  
  set(CLI11_MULTIONLY_TESTS TimerTest)
  
diff --git a/debian/patches/0003_cosmetic_make_blhc_happy.patch b/debian/patches/0003_cosmetic_make_blhc_happy.patch
index d953716..94ff21d 100644
--- a/debian/patches/0003_cosmetic_make_blhc_happy.patch
+++ b/debian/patches/0003_cosmetic_make_blhc_happy.patch
@@ -3,10 +3,10 @@ Author: Jose Luis Rivero <jrivero@osrfoundation.org>
 Last-Update: 2021-08-13
 Forwarded: No
 
-diff --git a/tests/informational.cpp b/tests/informational.cpp
-index 92f7dc4..a36b00c 100644
---- a/tests/informational.cpp
-+++ b/tests/informational.cpp
+Index: cli11.git/tests/informational.cpp
+===================================================================
+--- cli11.git.orig/tests/informational.cpp
++++ cli11.git/tests/informational.cpp
 @@ -15,7 +15,7 @@
  int main() {
      std::cout << "\nCLI11 information:\n";
diff --git a/docs/mainpage.md b/docs/mainpage.md
index b1f2b39..e0cf038 100644
--- a/docs/mainpage.md
+++ b/docs/mainpage.md
@@ -1,22 +1,24 @@
 # Introduction {#mainpage}
 
-This is the Doxygen API documentation for CLI11 parser. There is a friendly introduction to CLI11 on the [GitHub page](https://github.com/CLIUtils/CLI11), and [a tutorial series](https://cliutils.github.io/CLI11/book/).
+This is the Doxygen API documentation for CLI11 parser. There is a friendly
+introduction to CLI11 on the [GitHub page](https://github.com/CLIUtils/CLI11),
+and [a tutorial series](https://cliutils.github.io/CLI11/book/).
 
 The main classes are:
 
-| Name          | Where used                          |
-|---------------|-------------------------------------|
-|CLI::Option    | Options, stored in the app          |
-|CLI::App       | The main application or subcommands |
-|CLI::Validator | A check that can affect the type name |
-|CLI::Formatter | A subclassable formatter for help printing |
-|CLI::ExitCode  | A scoped enum with exit codes       |
-|CLI::Timer     | A timer class, only in CLI/Timer.hpp (not in `CLI11.hpp`) |
-|CLI::AutoTimer | A timer that prints on deletion     |
+| Name           | Where used                                                |
+| -------------- | --------------------------------------------------------- |
+| CLI::Option    | Options, stored in the app                                |
+| CLI::App       | The main application or subcommands                       |
+| CLI::Validator | A check that can affect the type name                     |
+| CLI::Formatter | A subclassable formatter for help printing                |
+| CLI::ExitCode  | A scoped enum with exit codes                             |
+| CLI::Timer     | A timer class, only in CLI/Timer.hpp (not in `CLI11.hpp`) |
+| CLI::AutoTimer | A timer that prints on deletion                           |
 
 Groups of related topics:
 
 | Name                 | Description                                    |
-|----------------------|------------------------------------------------|
+| -------------------- | ---------------------------------------------- |
 | @ref error_group     | Errors that can be thrown                      |
 | @ref validator_group | Common validators used in CLI::Option::check() |
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index d0d45c6..131a9fd 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -1,5 +1,5 @@
 function(add_cli_exe T)
-  add_executable(${T} ${ARGN} ${CLI11_headers})
+  add_executable(${T} ${ARGN})
   target_link_libraries(${T} PUBLIC CLI11)
   set_property(TARGET ${T} PROPERTY FOLDER "Examples")
   if(CLI11_FORCE_LIBCXX)
@@ -8,9 +8,6 @@ function(add_cli_exe T)
       APPEND_STRING
       PROPERTY LINK_FLAGS -stdlib=libc++)
   endif()
-  if(CLI11_CLANG_TIDY)
-    set_property(TARGET ${T} PROPERTY CXX_CLANG_TIDY "${DO_CLANG_TIDY}")
-  endif()
 endfunction()
 
 if(CLI11_BUILD_EXAMPLES_JSON)
@@ -247,3 +244,8 @@ set_property(TEST retired_deprecated PROPERTY PASS_REGULAR_EXPRESSION "deprecate
 add_cli_exe(custom_parse custom_parse.cpp)
 add_test(NAME cp_test COMMAND custom_parse --dv 1.7)
 set_property(TEST cp_test PROPERTY PASS_REGULAR_EXPRESSION "called correct")
+
+#------------------------------------------------
+# This executable is for manual testing and is expected to change regularly
+
+add_cli_exe(tester testEXE.cpp)
diff --git a/examples/callback_passthrough.cpp b/examples/callback_passthrough.cpp
index 48a2487..1aac0df 100644
--- a/examples/callback_passthrough.cpp
+++ b/examples/callback_passthrough.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/examples/config_app.cpp b/examples/config_app.cpp
index aec9fef..a0426ad 100644
--- a/examples/config_app.cpp
+++ b/examples/config_app.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/examples/custom_parse.cpp b/examples/custom_parse.cpp
index 44fc77a..eaaedd5 100644
--- a/examples/custom_parse.cpp
+++ b/examples/custom_parse.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -22,7 +22,7 @@ template <class T = int> struct Values {
 using DoubleValues = Values<double>;
 
 // the lexical cast operator should be in the same namespace as the type for ADL to work properly
-bool lexical_cast(const std::string &input, Values<double> &v) {
+bool lexical_cast(const std::string &input, Values<double> & /*v*/) {
     std::cout << "called correct lexical_cast function ! val: " << input << std::endl;
     return true;
 }
diff --git a/examples/digit_args.cpp b/examples/digit_args.cpp
index af0891a..a0785dd 100644
--- a/examples/digit_args.cpp
+++ b/examples/digit_args.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/examples/enum.cpp b/examples/enum.cpp
index 09b7f04..133adde 100644
--- a/examples/enum.cpp
+++ b/examples/enum.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/examples/enum_ostream.cpp b/examples/enum_ostream.cpp
index 4e3e6b2..1f8ac57 100644
--- a/examples/enum_ostream.cpp
+++ b/examples/enum_ostream.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/examples/formatter.cpp b/examples/formatter.cpp
index 3b2e3dc..4973cf9 100644
--- a/examples/formatter.cpp
+++ b/examples/formatter.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -24,9 +24,9 @@ int main(int argc, char **argv) {
 
     app.add_flag("--flag", "This is a flag");
 
-    auto sub1 = app.add_subcommand("one", "Description One");
+    auto *sub1 = app.add_subcommand("one", "Description One");
     sub1->add_flag("--oneflag", "Some flag");
-    auto sub2 = app.add_subcommand("two", "Description Two");
+    auto *sub2 = app.add_subcommand("two", "Description Two");
     sub2->add_flag("--twoflag", "Some other flag");
 
     CLI11_PARSE(app, argc, argv);
diff --git a/examples/groups.cpp b/examples/groups.cpp
index c766d6b..09c5d6b 100644
--- a/examples/groups.cpp
+++ b/examples/groups.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -10,7 +10,7 @@
 #include <string>
 
 int main(int argc, char **argv) {
-    CLI::AutoTimer("This is a timer");
+    CLI::AutoTimer give_me_a_name("This is a timer");
 
     CLI::App app("K3Pi goofit fitter");
 
diff --git a/examples/inter_argument_order.cpp b/examples/inter_argument_order.cpp
index ebee397..e8c489c 100644
--- a/examples/inter_argument_order.cpp
+++ b/examples/inter_argument_order.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -14,10 +14,10 @@ int main(int argc, char **argv) {
     CLI::App app{"An app to practice mixing unlimited arguments, but still recover the original order."};
 
     std::vector<int> foos;
-    auto foo = app.add_option("--foo,-f", foos, "Some unlimited argument");
+    auto *foo = app.add_option("--foo,-f", foos, "Some unlimited argument");
 
     std::vector<int> bars;
-    auto bar = app.add_option("--bar", bars, "Some unlimited argument");
+    auto *bar = app.add_option("--bar", bars, "Some unlimited argument");
 
     app.add_flag("--z,--x", "Random other flags");
 
@@ -33,7 +33,7 @@ int main(int argc, char **argv) {
     std::reverse(std::begin(bars), std::end(bars));
 
     std::vector<std::pair<std::string, int>> keyval;
-    for(auto option : app.parse_order()) {
+    for(auto *option : app.parse_order()) {
         if(option == foo) {
             keyval.emplace_back("foo", foos.back());
             foos.pop_back();
diff --git a/examples/modhelp.cpp b/examples/modhelp.cpp
index 9c67430..d0f8cf8 100644
--- a/examples/modhelp.cpp
+++ b/examples/modhelp.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -16,7 +16,7 @@ Note that this will not shortcut `->required` and other similar options.)raw"};
     test.set_help_flag();
 
     // Add custom flag that activates help
-    auto help = test.add_flag("-h,--help", "Request help");
+    auto *help = test.add_flag("-h,--help", "Request help");
 
     std::string some_option;
     test.add_option("-a", some_option, "Some description");
diff --git a/examples/nested.cpp b/examples/nested.cpp
index dc8323f..3587023 100644
--- a/examples/nested.cpp
+++ b/examples/nested.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/examples/option_groups.cpp b/examples/option_groups.cpp
index acc6574..3a28253 100644
--- a/examples/option_groups.cpp
+++ b/examples/option_groups.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -13,8 +13,8 @@ int main(int argc, char **argv) {
     CLI::App app("data output specification");
     app.set_help_all_flag("--help-all", "Expand all help");
 
-    auto format = app.add_option_group("output_format", "formatting type for output");
-    auto target = app.add_option_group("output target", "target location for the output");
+    auto *format = app.add_option_group("output_format", "formatting type for output");
+    auto *target = app.add_option_group("output target", "target location for the output");
     bool csv{false};
     bool human{false};
     bool binary{false};
@@ -33,10 +33,10 @@ int main(int argc, char **argv) {
     CLI11_PARSE(app, argc, argv);
 
     std::string format_type = (csv) ? std::string("CSV") : ((human) ? "human readable" : "binary");
-    std::cout << "Selected  " << format_type << "format" << std::endl;
-    if(fileLoc.empty()) {
+    std::cout << "Selected " << format_type << " format" << std::endl;
+    if(!fileLoc.empty()) {
         std::cout << " sent to file " << fileLoc << std::endl;
-    } else if(networkAddress.empty()) {
+    } else if(!networkAddress.empty()) {
         std::cout << " sent over network to " << networkAddress << std::endl;
     } else {
         std::cout << " sent to std::cout" << std::endl;
diff --git a/examples/positional_arity.cpp b/examples/positional_arity.cpp
index e9238fc..d2d9b9c 100644
--- a/examples/positional_arity.cpp
+++ b/examples/positional_arity.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -12,8 +12,8 @@ int main(int argc, char **argv) {
 
     CLI::App app("test for positional arity");
 
-    auto numbers = app.add_option_group("numbers", "specify key numbers");
-    auto files = app.add_option_group("files", "specify files");
+    auto *numbers = app.add_option_group("numbers", "specify key numbers");
+    auto *files = app.add_option_group("files", "specify files");
     int num1{-1}, num2{-1};
     numbers->add_option("num1", num1, "first number");
     numbers->add_option("num2", num2, "second number");
diff --git a/examples/positional_validation.cpp b/examples/positional_validation.cpp
index afc03fa..6b552da 100644
--- a/examples/positional_validation.cpp
+++ b/examples/positional_validation.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/examples/prefix_command.cpp b/examples/prefix_command.cpp
index 2af1a87..843f403 100644
--- a/examples/prefix_command.cpp
+++ b/examples/prefix_command.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -27,7 +27,7 @@ int main(int argc, char **argv) {
 
     std::cout << std::endl << "Remaining commands: ";
 
-    for(auto com : more_comms)
+    for(const auto &com : more_comms)
         std::cout << com << " ";
     std::cout << std::endl;
 
diff --git a/examples/ranges.cpp b/examples/ranges.cpp
index 6b4c1e3..ec14905 100644
--- a/examples/ranges.cpp
+++ b/examples/ranges.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -15,7 +15,7 @@ int main(int argc, char **argv) {
     std::vector<int> range;
     app.add_option("--range,-R", range, "A range")->expected(-2);
 
-    auto ogroup = app.add_option_group("min_max_step", "set the min max and step");
+    auto *ogroup = app.add_option_group("min_max_step", "set the min max and step");
     int min{0}, max{0}, step{1};
     ogroup->add_option("--min,-m", min, "The minimum")->required();
     ogroup->add_option("--max,-M", max, "The maximum")->required();
diff --git a/examples/retired.cpp b/examples/retired.cpp
index 3a18db4..28f61da 100644
--- a/examples/retired.cpp
+++ b/examples/retired.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -14,10 +14,10 @@ int main(int argc, char **argv) {
 
     CLI::App app("example for retired/deprecated options");
     std::vector<int> x;
-    auto opt1 = app.add_option("--retired_option2", x);
+    auto *opt1 = app.add_option("--retired_option2", x);
 
     std::pair<int, int> y;
-    auto opt2 = app.add_option("--deprecate", y);
+    auto *opt2 = app.add_option("--deprecate", y);
 
     app.add_option("--not_deprecated", x);
 
diff --git a/examples/shapes.cpp b/examples/shapes.cpp
index b318df0..d3f48ac 100644
--- a/examples/shapes.cpp
+++ b/examples/shapes.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -13,7 +13,7 @@ int main(int argc, char **argv) {
     CLI::App app("load shapes");
 
     app.set_help_all_flag("--help-all");
-    auto circle = app.add_subcommand("circle", "draw a circle")->immediate_callback();
+    auto *circle = app.add_subcommand("circle", "draw a circle")->immediate_callback();
     double radius{0.0};
     int circle_counter{0};
     circle->callback([&radius, &circle_counter] {
@@ -23,7 +23,7 @@ int main(int argc, char **argv) {
 
     circle->add_option("radius", radius, "the radius of the circle")->required();
 
-    auto rect = app.add_subcommand("rectangle", "draw a rectangle")->immediate_callback();
+    auto *rect = app.add_subcommand("rectangle", "draw a rectangle")->immediate_callback();
     double edge1{0.0};
     double edge2{0.0};
     int rect_counter{0};
@@ -39,7 +39,7 @@ int main(int argc, char **argv) {
     rect->add_option("edge1", edge1, "the first edge length of the rectangle")->required();
     rect->add_option("edge2", edge2, "the second edge length of the rectangle");
 
-    auto tri = app.add_subcommand("triangle", "draw a rectangle")->immediate_callback();
+    auto *tri = app.add_subcommand("triangle", "draw a rectangle")->immediate_callback();
     std::vector<double> sides;
     int tri_counter = 0;
     tri->callback([&sides, &tri_counter] {
diff --git a/examples/simple.cpp b/examples/simple.cpp
index 0f90442..b7095dd 100644
--- a/examples/simple.cpp
+++ b/examples/simple.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/examples/subcom_help.cpp b/examples/subcom_help.cpp
index 89af131..65030eb 100644
--- a/examples/subcom_help.cpp
+++ b/examples/subcom_help.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/examples/subcom_in_files/subcommand_a.cpp b/examples/subcom_in_files/subcommand_a.cpp
index 6b22983..bb1a6a1 100644
--- a/examples/subcom_in_files/subcommand_a.cpp
+++ b/examples/subcom_in_files/subcommand_a.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -15,7 +15,7 @@
 void setup_subcommand_a(CLI::App &app) {
     // Create the option and subcommand objects.
     auto opt = std::make_shared<SubcommandAOptions>();
-    auto sub = app.add_subcommand("subcommand_a", "performs subcommand a");
+    auto *sub = app.add_subcommand("subcommand_a", "performs subcommand a");
 
     // Add options to sub, binding them to opt.
     sub->add_option("-f,--file", opt->file, "File name")->required();
diff --git a/examples/subcom_in_files/subcommand_a.hpp b/examples/subcom_in_files/subcommand_a.hpp
index 116160c..6a8395d 100644
--- a/examples/subcom_in_files/subcommand_a.hpp
+++ b/examples/subcom_in_files/subcommand_a.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/examples/subcom_in_files/subcommand_main.cpp b/examples/subcom_in_files/subcommand_main.cpp
index 62b6380..e65339c 100644
--- a/examples/subcom_in_files/subcommand_main.cpp
+++ b/examples/subcom_in_files/subcommand_main.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/examples/subcom_partitioned.cpp b/examples/subcom_partitioned.cpp
index df48e2d..b6273ea 100644
--- a/examples/subcom_partitioned.cpp
+++ b/examples/subcom_partitioned.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -11,7 +11,7 @@
 #include <string>
 
 int main(int argc, char **argv) {
-    CLI::AutoTimer("This is a timer");
+    CLI::AutoTimer give_me_a_name("This is a timer");
 
     CLI::App app("K3Pi goofit fitter");
 
diff --git a/examples/subcommands.cpp b/examples/subcommands.cpp
index 68f163a..e69c04e 100644
--- a/examples/subcommands.cpp
+++ b/examples/subcommands.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -28,7 +28,7 @@ int main(int argc, char **argv) {
     std::cout << "Working on --count from stop: " << s->count() << ", direct count: " << stop->count("--count")
               << std::endl;
     std::cout << "Count of --random flag: " << app.count("--random") << std::endl;
-    for(auto subcom : app.get_subcommands())
+    for(auto *subcom : app.get_subcommands())
         std::cout << "Subcommand: " << subcom->get_name() << std::endl;
 
     return 0;
diff --git a/examples/testEXE.cpp b/examples/testEXE.cpp
new file mode 100644
index 0000000..b2cac7f
--- /dev/null
+++ b/examples/testEXE.cpp
@@ -0,0 +1,26 @@
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Code modified from https://github.com/CLIUtils/CLI11/issues/559
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <string>
+
+int main(int argc, const char *argv[]) {
+
+    int logLevel{0};
+    CLI::App app{"Test App"};
+
+    app.add_option("-v", logLevel, "level");
+
+    auto *subcom = app.add_subcommand("sub", "")->fallthrough();
+    subcom->preparse_callback([&app](size_t) { app.get_subcommand("sub")->add_option_group("group"); });
+
+    CLI11_PARSE(app, argc, argv);
+
+    std::cout << "level: " << logLevel << std::endl;
+}
diff --git a/examples/validators.cpp b/examples/validators.cpp
index 6ca8d38..87eb07a 100644
--- a/examples/validators.cpp
+++ b/examples/validators.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp
index 803f0f7..979237f 100644
--- a/include/CLI/App.hpp
+++ b/include/CLI/App.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -49,8 +49,11 @@ struct AppFriend;
 }  // namespace detail
 
 namespace FailureMessage {
-std::string simple(const App *app, const Error &e);
-std::string help(const App *app, const Error &e);
+/// Printout a clean, simple message on error (the default in CLI11 1.5+)
+CLI11_INLINE std::string simple(const App *app, const Error &e);
+
+/// Printout the full help string on error (if this fn is set, the old default for CLI11)
+CLI11_INLINE std::string help(const App *app, const Error &e);
 }  // namespace FailureMessage
 
 /// enumeration of modes of how to deal with extras in config files
@@ -61,6 +64,22 @@ class App;
 
 using App_p = std::shared_ptr<App>;
 
+namespace detail {
+/// helper functions for adding in appropriate flag modifiers for add_flag
+
+template <typename T, enable_if_t<!std::is_integral<T>::value || (sizeof(T) <= 1U), detail::enabler> = detail::dummy>
+Option *default_flag_modifiers(Option *opt) {
+    return opt->always_capture_default();
+}
+
+/// summing modifiers
+template <typename T, enable_if_t<std::is_integral<T>::value && (sizeof(T) > 1U), detail::enabler> = detail::dummy>
+Option *default_flag_modifiers(Option *opt) {
+    return opt->multi_option_policy(MultiOptionPolicy::Sum)->default_str("0")->force_callback();
+}
+
+}  // namespace detail
+
 class Option_group;
 /// Creates a command line program, with very few defaults.
 /** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated
@@ -222,6 +241,9 @@ class App {
     /// If set to true positional options are validated before assigning INHERITABLE
     bool validate_positionals_{false};
 
+    /// If set to true optional vector arguments are validated before assigning INHERITABLE
+    bool validate_optional_arguments_{false};
+
     /// indicator that the subcommand is silent and won't show up in subcommands list
     /// This is potentially useful as a modifier subcommand
     bool silent_{false};
@@ -263,38 +285,7 @@ class App {
     ///@}
 
     /// Special private constructor for subcommand
-    App(std::string app_description, std::string app_name, App *parent)
-        : name_(std::move(app_name)), description_(std::move(app_description)), parent_(parent) {
-        // Inherit if not from a nullptr
-        if(parent_ != nullptr) {
-            if(parent_->help_ptr_ != nullptr)
-                set_help_flag(parent_->help_ptr_->get_name(false, true), parent_->help_ptr_->get_description());
-            if(parent_->help_all_ptr_ != nullptr)
-                set_help_all_flag(parent_->help_all_ptr_->get_name(false, true),
-                                  parent_->help_all_ptr_->get_description());
-
-            /// OptionDefaults
-            option_defaults_ = parent_->option_defaults_;
-
-            // INHERITABLE
-            failure_message_ = parent_->failure_message_;
-            allow_extras_ = parent_->allow_extras_;
-            allow_config_extras_ = parent_->allow_config_extras_;
-            prefix_command_ = parent_->prefix_command_;
-            immediate_callback_ = parent_->immediate_callback_;
-            ignore_case_ = parent_->ignore_case_;
-            ignore_underscore_ = parent_->ignore_underscore_;
-            fallthrough_ = parent_->fallthrough_;
-            validate_positionals_ = parent_->validate_positionals_;
-            configurable_ = parent_->configurable_;
-            allow_windows_style_options_ = parent_->allow_windows_style_options_;
-            group_ = parent_->group_;
-            footer_ = parent_->footer_;
-            formatter_ = parent_->formatter_;
-            config_formatter_ = parent_->config_formatter_;
-            require_subcommand_max_ = parent_->require_subcommand_max_;
-        }
-    }
+    App(std::string app_description, std::string app_name, App *parent);
 
   public:
     /// @name Basic
@@ -349,41 +340,10 @@ class App {
     }
 
     /// Set a name for the app (empty will use parser to set the name)
-    App *name(std::string app_name = "") {
-
-        if(parent_ != nullptr) {
-            auto oname = name_;
-            name_ = app_name;
-            auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent());
-            if(!res.empty()) {
-                name_ = oname;
-                throw(OptionAlreadyAdded(app_name + " conflicts with existing subcommand names"));
-            }
-        } else {
-            name_ = app_name;
-        }
-        has_automatic_name_ = false;
-        return this;
-    }
+    App *name(std::string app_name = "");
 
     /// Set an alias for the app
-    App *alias(std::string app_name) {
-        if(app_name.empty() || !detail::valid_alias_name_string(app_name)) {
-            throw IncorrectConstruction("Aliases may not be empty or contain newlines or null characters");
-        }
-        if(parent_ != nullptr) {
-            aliases_.push_back(app_name);
-            auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent());
-            if(!res.empty()) {
-                aliases_.pop_back();
-                throw(OptionAlreadyAdded("alias already matches an existing subcommand: " + app_name));
-            }
-        } else {
-            aliases_.push_back(app_name);
-        }
-
-        return this;
-    }
+    App *alias(std::string app_name);
 
     /// Remove the error when extras are left over on the command line.
     App *allow_extras(bool allow = true) {
@@ -432,17 +392,7 @@ class App {
     }
 
     /// Set the subcommand callback to be executed immediately on subcommand completion
-    App *immediate_callback(bool immediate = true) {
-        immediate_callback_ = immediate;
-        if(immediate_callback_) {
-            if(final_callback_ && !(parse_complete_callback_)) {
-                std::swap(final_callback_, parse_complete_callback_);
-            }
-        } else if(!(final_callback_) && parse_complete_callback_) {
-            std::swap(final_callback_, parse_complete_callback_);
-        }
-        return this;
-    }
+    App *immediate_callback(bool immediate = true);
 
     /// Set the subcommand to validate positional arguments before assigning
     App *validate_positionals(bool validate = true) {
@@ -450,6 +400,12 @@ class App {
         return this;
     }
 
+    /// Set the subcommand to validate optional vector arguments before assigning
+    App *validate_optional_arguments(bool validate = true) {
+        validate_optional_arguments_ = validate;
+        return this;
+    }
+
     /// ignore extras in config files
     App *allow_config_extras(bool allow = true) {
         if(allow) {
@@ -474,19 +430,7 @@ class App {
     }
 
     /// Ignore case. Subcommands inherit value.
-    App *ignore_case(bool value = true) {
-        if(value && !ignore_case_) {
-            ignore_case_ = true;
-            auto *p = (parent_ != nullptr) ? _get_fallthrough_parent() : this;
-            auto &match = _compare_subcommand_names(*this, *p);
-            if(!match.empty()) {
-                ignore_case_ = false;  // we are throwing so need to be exception invariant
-                throw OptionAlreadyAdded("ignore case would cause subcommand name conflicts: " + match);
-            }
-        }
-        ignore_case_ = value;
-        return this;
-    }
+    App *ignore_case(bool value = true);
 
     /// Allow windows style options, such as `/opt`. First matching short or long name used. Subcommands inherit
     /// value.
@@ -508,19 +452,7 @@ class App {
     }
 
     /// Ignore underscore. Subcommands inherit value.
-    App *ignore_underscore(bool value = true) {
-        if(value && !ignore_underscore_) {
-            ignore_underscore_ = true;
-            auto *p = (parent_ != nullptr) ? _get_fallthrough_parent() : this;
-            auto &match = _compare_subcommand_names(*this, *p);
-            if(!match.empty()) {
-                ignore_underscore_ = false;
-                throw OptionAlreadyAdded("ignore underscore would cause subcommand name conflicts: " + match);
-            }
-        }
-        ignore_underscore_ = value;
-        return this;
-    }
+    App *ignore_underscore(bool value = true);
 
     /// Set the help formatter
     App *formatter(std::shared_ptr<FormatterBase> fmt) {
@@ -541,7 +473,7 @@ class App {
     }
 
     /// Check to see if this subcommand was parsed, true only if received on command line.
-    bool parsed() const { return parsed_ > 0; }
+    CLI11_NODISCARD bool parsed() const { return parsed_ > 0; }
 
     /// Get the OptionDefault object, to set option defaults
     OptionDefaults *option_defaults() { return &option_defaults_; }
@@ -568,42 +500,7 @@ class App {
                        callback_t option_callback,
                        std::string option_description = "",
                        bool defaulted = false,
-                       std::function<std::string()> func = {}) {
-        Option myopt{option_name, option_description, option_callback, this};
-
-        if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) {
-               return *v == myopt;
-           }) == std::end(options_)) {
-            options_.emplace_back();
-            Option_p &option = options_.back();
-            option.reset(new Option(option_name, option_description, option_callback, this));
-
-            // Set the default string capture function
-            option->default_function(func);
-
-            // For compatibility with CLI11 1.7 and before, capture the default string here
-            if(defaulted)
-                option->capture_default_str();
-
-            // Transfer defaults to the new option
-            option_defaults_.copy_to(option.get());
-
-            // Don't bother to capture if we already did
-            if(!defaulted && option->get_always_capture_default())
-                option->capture_default_str();
-
-            return option.get();
-        }
-        // we know something matches now find what it is so we can produce more error information
-        for(auto &opt : options_) {
-            auto &matchname = opt->matching_name(myopt);
-            if(!matchname.empty()) {
-                throw(OptionAlreadyAdded("added option matched existing option name: " + matchname));
-            }
-        }
-        // this line should not be reached the above loop should trigger the throw
-        throw(OptionAlreadyAdded("added option matched existing option name"));  // LCOV_EXCL_LINE
-    }
+                       std::function<std::string()> func = {});
 
     /// Add option for assigning to a variable
     template <typename AssignTo,
@@ -685,103 +582,24 @@ class App {
     }
 
     /// Set a help flag, replace the existing one if present
-    Option *set_help_flag(std::string flag_name = "", const std::string &help_description = "") {
-        // take flag_description by const reference otherwise add_flag tries to assign to help_description
-        if(help_ptr_ != nullptr) {
-            remove_option(help_ptr_);
-            help_ptr_ = nullptr;
-        }
-
-        // Empty name will simply remove the help flag
-        if(!flag_name.empty()) {
-            help_ptr_ = add_flag(flag_name, help_description);
-            help_ptr_->configurable(false);
-        }
-
-        return help_ptr_;
-    }
+    Option *set_help_flag(std::string flag_name = "", const std::string &help_description = "");
 
     /// Set a help all flag, replaced the existing one if present
-    Option *set_help_all_flag(std::string help_name = "", const std::string &help_description = "") {
-        // take flag_description by const reference otherwise add_flag tries to assign to flag_description
-        if(help_all_ptr_ != nullptr) {
-            remove_option(help_all_ptr_);
-            help_all_ptr_ = nullptr;
-        }
-
-        // Empty name will simply remove the help all flag
-        if(!help_name.empty()) {
-            help_all_ptr_ = add_flag(help_name, help_description);
-            help_all_ptr_->configurable(false);
-        }
-
-        return help_all_ptr_;
-    }
+    Option *set_help_all_flag(std::string help_name = "", const std::string &help_description = "");
 
     /// Set a version flag and version display string, replace the existing one if present
     Option *set_version_flag(std::string flag_name = "",
                              const std::string &versionString = "",
-                             const std::string &version_help = "Display program version information and exit") {
-        // take flag_description by const reference otherwise add_flag tries to assign to version_description
-        if(version_ptr_ != nullptr) {
-            remove_option(version_ptr_);
-            version_ptr_ = nullptr;
-        }
+                             const std::string &version_help = "Display program version information and exit");
 
-        // Empty name will simply remove the version flag
-        if(!flag_name.empty()) {
-            version_ptr_ = add_flag_callback(
-                flag_name, [versionString]() { throw(CLI::CallForVersion(versionString, 0)); }, version_help);
-            version_ptr_->configurable(false);
-        }
-
-        return version_ptr_;
-    }
     /// Generate the version string through a callback function
     Option *set_version_flag(std::string flag_name,
                              std::function<std::string()> vfunc,
-                             const std::string &version_help = "Display program version information and exit") {
-        if(version_ptr_ != nullptr) {
-            remove_option(version_ptr_);
-            version_ptr_ = nullptr;
-        }
-
-        // Empty name will simply remove the version flag
-        if(!flag_name.empty()) {
-            version_ptr_ = add_flag_callback(
-                flag_name, [vfunc]() { throw(CLI::CallForVersion(vfunc(), 0)); }, version_help);
-            version_ptr_->configurable(false);
-        }
-
-        return version_ptr_;
-    }
+                             const std::string &version_help = "Display program version information and exit");
 
   private:
     /// Internal function for adding a flag
-    Option *_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description) {
-        Option *opt;
-        if(detail::has_default_flag_values(flag_name)) {
-            // check for default values and if it has them
-            auto flag_defaults = detail::get_default_flag_values(flag_name);
-            detail::remove_default_flag_values(flag_name);
-            opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false);
-            for(const auto &fname : flag_defaults)
-                opt->fnames_.push_back(fname.first);
-            opt->default_flag_values_ = std::move(flag_defaults);
-        } else {
-            opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false);
-        }
-        // flags cannot have positional values
-        if(opt->get_positional()) {
-            auto pos_name = opt->get_name(true);
-            remove_option(opt);
-            throw IncorrectConstruction::PositionalFlag(pos_name);
-        }
-        opt->multi_option_policy(MultiOptionPolicy::TakeLast);
-        opt->expected(0);
-        opt->required(false);
-        return opt;
-    }
+    Option *_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description);
 
   public:
     /// Add a flag with no description or variable assignment
@@ -797,42 +615,22 @@ class App {
         return _add_flag_internal(flag_name, CLI::callback_t(), flag_description);
     }
 
-    /// Add option for flag with integer result - defaults to allowing multiple passings, but can be forced to one
-    /// if `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used.
-    template <typename T,
-              enable_if_t<std::is_constructible<T, std::int64_t>::value && !is_bool<T>::value, detail::enabler> =
-                  detail::dummy>
-    Option *add_flag(std::string flag_name,
-                     T &flag_count,  ///< A variable holding the count
-                     std::string flag_description = "") {
-        flag_count = 0;
-        CLI::callback_t fun = [&flag_count](const CLI::results_t &res) {
-            try {
-                detail::sum_flag_vector(res, flag_count);
-            } catch(const std::invalid_argument &) {
-                return false;
-            }
-            return true;
-        };
-        return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))
-            ->multi_option_policy(MultiOptionPolicy::TakeAll);
-    }
-
     /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes
     /// that can be converted from a string
     template <typename T,
               enable_if_t<!detail::is_mutable_container<T>::value && !std::is_const<T>::value &&
-                              (!std::is_constructible<T, std::int64_t>::value || is_bool<T>::value) &&
                               !std::is_constructible<std::function<void(int)>, T>::value,
                           detail::enabler> = detail::dummy>
     Option *add_flag(std::string flag_name,
-                     T &flag_result,  ///< A variable holding true if passed
+                     T &flag_result,  ///< A variable holding the flag result
                      std::string flag_description = "") {
 
         CLI::callback_t fun = [&flag_result](const CLI::results_t &res) {
-            return CLI::detail::lexical_cast(res[0], flag_result);
+            using CLI::detail::lexical_cast;
+            return lexical_cast(res[0], flag_result);
         };
-        return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))->run_callback_for_default();
+        auto *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
+        return detail::default_flag_modifiers<T>(opt);
     }
 
     /// Vector version to capture multiple flags.
@@ -845,8 +643,9 @@ class App {
         CLI::callback_t fun = [&flag_results](const CLI::results_t &res) {
             bool retval = true;
             for(const auto &elem : res) {
+                using CLI::detail::lexical_cast;
                 flag_results.emplace_back();
-                retval &= detail::lexical_cast(elem, flag_results.back());
+                retval &= lexical_cast(elem, flag_results.back());
             }
             return retval;
         };
@@ -858,33 +657,12 @@ class App {
     /// Add option for callback that is triggered with a true flag and takes no arguments
     Option *add_flag_callback(std::string flag_name,
                               std::function<void(void)> function,  ///< A function to call, void(void)
-                              std::string flag_description = "") {
-
-        CLI::callback_t fun = [function](const CLI::results_t &res) {
-            bool trigger{false};
-            auto result = CLI::detail::lexical_cast(res[0], trigger);
-            if(result && trigger) {
-                function();
-            }
-            return result;
-        };
-        return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
-    }
+                              std::string flag_description = "");
 
     /// Add option for callback with an integer value
     Option *add_flag_function(std::string flag_name,
                               std::function<void(std::int64_t)> function,  ///< A function to call, void(int)
-                              std::string flag_description = "") {
-
-        CLI::callback_t fun = [function](const CLI::results_t &res) {
-            std::int64_t flag_count = 0;
-            detail::sum_flag_vector(res, flag_count);
-            function(flag_count);
-            return true;
-        };
-        return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))
-            ->multi_option_policy(MultiOptionPolicy::TakeAll);
-    }
+                              std::string flag_description = "");
 
 #ifdef CLI11_CPP14
     /// Add option for callback (C++14 or better only)
@@ -899,50 +677,10 @@ class App {
     Option *set_config(std::string option_name = "",
                        std::string default_filename = "",
                        const std::string &help_message = "Read an ini file",
-                       bool config_required = false) {
-
-        // Remove existing config if present
-        if(config_ptr_ != nullptr) {
-            remove_option(config_ptr_);
-            config_ptr_ = nullptr;  // need to remove the config_ptr completely
-        }
-
-        // Only add config if option passed
-        if(!option_name.empty()) {
-            config_ptr_ = add_option(option_name, help_message);
-            if(config_required) {
-                config_ptr_->required();
-            }
-            if(!default_filename.empty()) {
-                config_ptr_->default_str(std::move(default_filename));
-            }
-            config_ptr_->configurable(false);
-        }
-
-        return config_ptr_;
-    }
+                       bool config_required = false);
 
     /// Removes an option from the App. Takes an option pointer. Returns true if found and removed.
-    bool remove_option(Option *opt) {
-        // Make sure no links exist
-        for(Option_p &op : options_) {
-            op->remove_needs(opt);
-            op->remove_excludes(opt);
-        }
-
-        if(help_ptr_ == opt)
-            help_ptr_ = nullptr;
-        if(help_all_ptr_ == opt)
-            help_all_ptr_ = nullptr;
-
-        auto iterator =
-            std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; });
-        if(iterator != std::end(options_)) {
-            options_.erase(iterator);
-            return true;
-        }
-        return false;
-    }
+    bool remove_option(Option *opt);
 
     /// creates an option group as part of the given app
     template <typename T = Option_group>
@@ -951,7 +689,7 @@ class App {
             throw IncorrectConstruction("option group names may not contain newlines or null characters");
         }
         auto option_group = std::make_shared<T>(std::move(group_description), group_name, this);
-        auto ptr = option_group.get();
+        auto *ptr = option_group.get();
         // move to App_p for overload resolution on older gcc versions
         App_p app_ptr = std::dynamic_pointer_cast<App>(option_group);
         add_subcommand(std::move(app_ptr));
@@ -963,140 +701,44 @@ class App {
     ///@{
 
     /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag
-    App *add_subcommand(std::string subcommand_name = "", std::string subcommand_description = "") {
-        if(!subcommand_name.empty() && !detail::valid_name_string(subcommand_name)) {
-            if(!detail::valid_first_char(subcommand_name[0])) {
-                throw IncorrectConstruction(
-                    "Subcommand name starts with invalid character, '!' and '-' are not allowed");
-            }
-            for(auto c : subcommand_name) {
-                if(!detail::valid_later_char(c)) {
-                    throw IncorrectConstruction(std::string("Subcommand name contains invalid character ('") + c +
-                                                "'), all characters are allowed except"
-                                                "'=',':','{','}', and ' '");
-                }
-            }
-        }
-        CLI::App_p subcom = std::shared_ptr<App>(new App(std::move(subcommand_description), subcommand_name, this));
-        return add_subcommand(std::move(subcom));
-    }
+    App *add_subcommand(std::string subcommand_name = "", std::string subcommand_description = "");
 
     /// Add a previously created app as a subcommand
-    App *add_subcommand(CLI::App_p subcom) {
-        if(!subcom)
-            throw IncorrectConstruction("passed App is not valid");
-        auto ckapp = (name_.empty() && parent_ != nullptr) ? _get_fallthrough_parent() : this;
-        auto &mstrg = _compare_subcommand_names(*subcom, *ckapp);
-        if(!mstrg.empty()) {
-            throw(OptionAlreadyAdded("subcommand name or alias matches existing subcommand: " + mstrg));
-        }
-        subcom->parent_ = this;
-        subcommands_.push_back(std::move(subcom));
-        return subcommands_.back().get();
-    }
+    App *add_subcommand(CLI::App_p subcom);
 
     /// Removes a subcommand from the App. Takes a subcommand pointer. Returns true if found and removed.
-    bool remove_subcommand(App *subcom) {
-        // Make sure no links exist
-        for(App_p &sub : subcommands_) {
-            sub->remove_excludes(subcom);
-            sub->remove_needs(subcom);
-        }
+    bool remove_subcommand(App *subcom);
 
-        auto iterator = std::find_if(
-            std::begin(subcommands_), std::end(subcommands_), [subcom](const App_p &v) { return v.get() == subcom; });
-        if(iterator != std::end(subcommands_)) {
-            subcommands_.erase(iterator);
-            return true;
-        }
-        return false;
-    }
     /// Check to see if a subcommand is part of this command (doesn't have to be in command line)
     /// returns the first subcommand if passed a nullptr
-    App *get_subcommand(const App *subcom) const {
-        if(subcom == nullptr)
-            throw OptionNotFound("nullptr passed");
-        for(const App_p &subcomptr : subcommands_)
-            if(subcomptr.get() == subcom)
-                return subcomptr.get();
-        throw OptionNotFound(subcom->get_name());
-    }
+    App *get_subcommand(const App *subcom) const;
 
     /// Check to see if a subcommand is part of this command (text version)
-    App *get_subcommand(std::string subcom) const {
-        auto subc = _find_subcommand(subcom, false, false);
-        if(subc == nullptr)
-            throw OptionNotFound(subcom);
-        return subc;
-    }
+    CLI11_NODISCARD App *get_subcommand(std::string subcom) const;
+
     /// Get a pointer to subcommand by index
-    App *get_subcommand(int index = 0) const {
-        if(index >= 0) {
-            auto uindex = static_cast<unsigned>(index);
-            if(uindex < subcommands_.size())
-                return subcommands_[uindex].get();
-        }
-        throw OptionNotFound(std::to_string(index));
-    }
+    CLI11_NODISCARD App *get_subcommand(int index = 0) const;
 
     /// Check to see if a subcommand is part of this command and get a shared_ptr to it
-    CLI::App_p get_subcommand_ptr(App *subcom) const {
-        if(subcom == nullptr)
-            throw OptionNotFound("nullptr passed");
-        for(const App_p &subcomptr : subcommands_)
-            if(subcomptr.get() == subcom)
-                return subcomptr;
-        throw OptionNotFound(subcom->get_name());
-    }
+    CLI::App_p get_subcommand_ptr(App *subcom) const;
 
     /// Check to see if a subcommand is part of this command (text version)
-    CLI::App_p get_subcommand_ptr(std::string subcom) const {
-        for(const App_p &subcomptr : subcommands_)
-            if(subcomptr->check_name(subcom))
-                return subcomptr;
-        throw OptionNotFound(subcom);
-    }
+    CLI11_NODISCARD CLI::App_p get_subcommand_ptr(std::string subcom) const;
 
     /// Get an owning pointer to subcommand by index
-    CLI::App_p get_subcommand_ptr(int index = 0) const {
-        if(index >= 0) {
-            auto uindex = static_cast<unsigned>(index);
-            if(uindex < subcommands_.size())
-                return subcommands_[uindex];
-        }
-        throw OptionNotFound(std::to_string(index));
-    }
+    CLI11_NODISCARD CLI::App_p get_subcommand_ptr(int index = 0) const;
 
     /// Check to see if an option group is part of this App
-    App *get_option_group(std::string group_name) const {
-        for(const App_p &app : subcommands_) {
-            if(app->name_.empty() && app->group_ == group_name) {
-                return app.get();
-            }
-        }
-        throw OptionNotFound(group_name);
-    }
+    CLI11_NODISCARD App *get_option_group(std::string group_name) const;
 
     /// No argument version of count counts the number of times this subcommand was
     /// passed in. The main app will return 1. Unnamed subcommands will also return 1 unless
     /// otherwise modified in a callback
-    std::size_t count() const { return parsed_; }
+    CLI11_NODISCARD std::size_t count() const { return parsed_; }
 
     /// Get a count of all the arguments processed in options and subcommands, this excludes arguments which were
     /// treated as extras.
-    std::size_t count_all() const {
-        std::size_t cnt{0};
-        for(auto &opt : options_) {
-            cnt += opt->count();
-        }
-        for(auto &sub : subcommands_) {
-            cnt += sub->count_all();
-        }
-        if(!get_name().empty()) {  // for named subcommands add the number of times the subcommand was called
-            cnt += parsed_;
-        }
-        return cnt;
-    }
+    CLI11_NODISCARD std::size_t count_all() const;
 
     /// Changes the group membership
     App *group(std::string group_name) {
@@ -1187,199 +829,53 @@ class App {
     ///@{
     //
     /// Reset the parsed data
-    void clear() {
-
-        parsed_ = 0;
-        pre_parse_called_ = false;
-
-        missing_.clear();
-        parsed_subcommands_.clear();
-        for(const Option_p &opt : options_) {
-            opt->clear();
-        }
-        for(const App_p &subc : subcommands_) {
-            subc->clear();
-        }
-    }
+    void clear();
 
     /// Parses the command line - throws errors.
     /// This must be called after the options are in but before the rest of the program.
-    void parse(int argc, const char *const *argv) {
-        // If the name is not set, read from command line
-        if(name_.empty() || has_automatic_name_) {
-            has_automatic_name_ = true;
-            name_ = argv[0];
-        }
-
-        std::vector<std::string> args;
-        args.reserve(static_cast<std::size_t>(argc) - 1);
-        for(int i = argc - 1; i > 0; i--)
-            args.emplace_back(argv[i]);
-        parse(std::move(args));
-    }
+    void parse(int argc, const char *const *argv);
 
     /// Parse a single string as if it contained command line arguments.
     /// This function splits the string into arguments then calls parse(std::vector<std::string> &)
     /// the function takes an optional boolean argument specifying if the programName is included in the string to
     /// process
-    void parse(std::string commandline, bool program_name_included = false) {
-
-        if(program_name_included) {
-            auto nstr = detail::split_program_name(commandline);
-            if((name_.empty()) || (has_automatic_name_)) {
-                has_automatic_name_ = true;
-                name_ = nstr.first;
-            }
-            commandline = std::move(nstr.second);
-        } else {
-            detail::trim(commandline);
-        }
-        // the next section of code is to deal with quoted arguments after an '=' or ':' for windows like operations
-        if(!commandline.empty()) {
-            commandline = detail::find_and_modify(commandline, "=", detail::escape_detect);
-            if(allow_windows_style_options_)
-                commandline = detail::find_and_modify(commandline, ":", detail::escape_detect);
-        }
-
-        auto args = detail::split_up(std::move(commandline));
-        // remove all empty strings
-        args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end());
-        std::reverse(args.begin(), args.end());
-
-        parse(std::move(args));
-    }
+    void parse(std::string commandline, bool program_name_included = false);
 
     /// The real work is done here. Expects a reversed vector.
     /// Changes the vector to the remaining options.
-    void parse(std::vector<std::string> &args) {
-        // Clear if parsed
-        if(parsed_ > 0)
-            clear();
-
-        // parsed_ is incremented in commands/subcommands,
-        // but placed here to make sure this is cleared when
-        // running parse after an error is thrown, even by _validate or _configure.
-        parsed_ = 1;
-        _validate();
-        _configure();
-        // set the parent as nullptr as this object should be the top now
-        parent_ = nullptr;
-        parsed_ = 0;
-
-        _parse(args);
-        run_callback();
-    }
+    void parse(std::vector<std::string> &args);
 
     /// The real work is done here. Expects a reversed vector.
-    void parse(std::vector<std::string> &&args) {
-        // Clear if parsed
-        if(parsed_ > 0)
-            clear();
-
-        // parsed_ is incremented in commands/subcommands,
-        // but placed here to make sure this is cleared when
-        // running parse after an error is thrown, even by _validate or _configure.
-        parsed_ = 1;
-        _validate();
-        _configure();
-        // set the parent as nullptr as this object should be the top now
-        parent_ = nullptr;
-        parsed_ = 0;
-
-        _parse(std::move(args));
-        run_callback();
-    }
-
-    void parse_from_stream(std::istream &input) {
-        if(parsed_ == 0) {
-            _validate();
-            _configure();
-            // set the parent as nullptr as this object should be the top now
-        }
+    void parse(std::vector<std::string> &&args);
+
+    void parse_from_stream(std::istream &input);
 
-        _parse_stream(input);
-        run_callback();
-    }
     /// Provide a function to print a help message. The function gets access to the App pointer and error.
     void failure_message(std::function<std::string(const App *, const Error &e)> function) {
         failure_message_ = function;
     }
 
     /// Print a nice error message and return the exit code
-    int exit(const Error &e, std::ostream &out = std::cout, std::ostream &err = std::cerr) const {
-
-        /// Avoid printing anything if this is a CLI::RuntimeError
-        if(e.get_name() == "RuntimeError")
-            return e.get_exit_code();
-
-        if(e.get_name() == "CallForHelp") {
-            out << help();
-            return e.get_exit_code();
-        }
-
-        if(e.get_name() == "CallForAllHelp") {
-            out << help("", AppFormatMode::All);
-            return e.get_exit_code();
-        }
-
-        if(e.get_name() == "CallForVersion") {
-            out << e.what() << std::endl;
-            return e.get_exit_code();
-        }
-
-        if(e.get_exit_code() != static_cast<int>(ExitCodes::Success)) {
-            if(failure_message_)
-                err << failure_message_(this, e) << std::flush;
-        }
-
-        return e.get_exit_code();
-    }
+    int exit(const Error &e, std::ostream &out = std::cout, std::ostream &err = std::cerr) const;
 
     ///@}
     /// @name Post parsing
     ///@{
 
     /// Counts the number of times the given option was passed.
-    std::size_t count(std::string option_name) const { return get_option(option_name)->count(); }
+    CLI11_NODISCARD std::size_t count(std::string option_name) const { return get_option(option_name)->count(); }
 
     /// Get a subcommand pointer list to the currently selected subcommands (after parsing by default, in command
     /// line order; use parsed = false to get the original definition list.)
-    std::vector<App *> get_subcommands() const { return parsed_subcommands_; }
+    CLI11_NODISCARD std::vector<App *> get_subcommands() const { return parsed_subcommands_; }
 
     /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all
     /// subcommands (const)
-    std::vector<const App *> get_subcommands(const std::function<bool(const App *)> &filter) const {
-        std::vector<const App *> subcomms(subcommands_.size());
-        std::transform(std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) {
-            return v.get();
-        });
-
-        if(filter) {
-            subcomms.erase(std::remove_if(std::begin(subcomms),
-                                          std::end(subcomms),
-                                          [&filter](const App *app) { return !filter(app); }),
-                           std::end(subcomms));
-        }
-
-        return subcomms;
-    }
+    std::vector<const App *> get_subcommands(const std::function<bool(const App *)> &filter) const;
 
     /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all
     /// subcommands
-    std::vector<App *> get_subcommands(const std::function<bool(App *)> &filter) {
-        std::vector<App *> subcomms(subcommands_.size());
-        std::transform(std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) {
-            return v.get();
-        });
-
-        if(filter) {
-            subcomms.erase(
-                std::remove_if(std::begin(subcomms), std::end(subcomms), [&filter](App *app) { return !filter(app); }),
-                std::end(subcomms));
-        }
-
-        return subcomms;
-    }
+    std::vector<App *> get_subcommands(const std::function<bool(App *)> &filter);
 
     /// Check to see if given subcommand was selected
     bool got_subcommand(const App *subcom) const {
@@ -1388,7 +884,9 @@ class App {
     }
 
     /// Check with name instead of pointer to see if subcommand was selected
-    bool got_subcommand(std::string subcommand_name) const { return get_subcommand(subcommand_name)->parsed_ > 0; }
+    CLI11_NODISCARD bool got_subcommand(std::string subcommand_name) const {
+        return get_subcommand(subcommand_name)->parsed_ > 0;
+    }
 
     /// Sets excluded options for the subcommand
     App *excludes(Option *opt) {
@@ -1435,47 +933,16 @@ class App {
     }
 
     /// Removes an option from the excludes list of this subcommand
-    bool remove_excludes(Option *opt) {
-        auto iterator = std::find(std::begin(exclude_options_), std::end(exclude_options_), opt);
-        if(iterator == std::end(exclude_options_)) {
-            return false;
-        }
-        exclude_options_.erase(iterator);
-        return true;
-    }
+    bool remove_excludes(Option *opt);
 
     /// Removes a subcommand from the excludes list of this subcommand
-    bool remove_excludes(App *app) {
-        auto iterator = std::find(std::begin(exclude_subcommands_), std::end(exclude_subcommands_), app);
-        if(iterator == std::end(exclude_subcommands_)) {
-            return false;
-        }
-        auto other_app = *iterator;
-        exclude_subcommands_.erase(iterator);
-        other_app->remove_excludes(this);
-        return true;
-    }
+    bool remove_excludes(App *app);
 
     /// Removes an option from the needs list of this subcommand
-    bool remove_needs(Option *opt) {
-        auto iterator = std::find(std::begin(need_options_), std::end(need_options_), opt);
-        if(iterator == std::end(need_options_)) {
-            return false;
-        }
-        need_options_.erase(iterator);
-        return true;
-    }
+    bool remove_needs(Option *opt);
 
     /// Removes a subcommand from the needs list of this subcommand
-    bool remove_needs(App *app) {
-        auto iterator = std::find(std::begin(need_subcommands_), std::end(need_subcommands_), app);
-        if(iterator == std::end(need_subcommands_)) {
-            return false;
-        }
-        need_subcommands_.erase(iterator);
-        return true;
-    }
-
+    bool remove_needs(App *app);
     ///@}
     /// @name Help
     ///@{
@@ -1492,57 +959,30 @@ class App {
     }
     /// Produce a string that could be read in as a config of the current values of the App. Set default_also to
     /// include default arguments. write_descriptions will print a description for the App and for each option.
-    std::string config_to_str(bool default_also = false, bool write_description = false) const {
+    CLI11_NODISCARD std::string config_to_str(bool default_also = false, bool write_description = false) const {
         return config_formatter_->to_config(this, default_also, write_description, "");
     }
 
     /// Makes a help message, using the currently configured formatter
     /// Will only do one subcommand at a time
-    std::string help(std::string prev = "", AppFormatMode mode = AppFormatMode::Normal) const {
-        if(prev.empty())
-            prev = get_name();
-        else
-            prev += " " + get_name();
-
-        // Delegate to subcommand if needed
-        auto selected_subcommands = get_subcommands();
-        if(!selected_subcommands.empty()) {
-            return selected_subcommands.at(0)->help(prev, mode);
-        }
-        return formatter_->make_help(this, prev, mode);
-    }
+    CLI11_NODISCARD std::string help(std::string prev = "", AppFormatMode mode = AppFormatMode::Normal) const;
 
     /// Displays a version string
-    std::string version() const {
-        std::string val;
-        if(version_ptr_ != nullptr) {
-            auto rv = version_ptr_->results();
-            version_ptr_->clear();
-            version_ptr_->add_result("true");
-            try {
-                version_ptr_->run_callback();
-            } catch(const CLI::CallForVersion &cfv) {
-                val = cfv.what();
-            }
-            version_ptr_->clear();
-            version_ptr_->add_result(rv);
-        }
-        return val;
-    }
+    CLI11_NODISCARD std::string version() const;
     ///@}
     /// @name Getters
     ///@{
 
     /// Access the formatter
-    std::shared_ptr<FormatterBase> get_formatter() const { return formatter_; }
+    CLI11_NODISCARD std::shared_ptr<FormatterBase> get_formatter() const { return formatter_; }
 
     /// Access the config formatter
-    std::shared_ptr<Config> get_config_formatter() const { return config_formatter_; }
+    CLI11_NODISCARD std::shared_ptr<Config> get_config_formatter() const { return config_formatter_; }
 
     /// Access the config formatter as a configBase pointer
-    std::shared_ptr<ConfigBase> get_config_formatter_base() const {
+    CLI11_NODISCARD std::shared_ptr<ConfigBase> get_config_formatter_base() const {
         // This is safer as a dynamic_cast if we have RTTI, as Config -> ConfigBase
-#if defined(__cpp_rtti) || (defined(__GXX_RTTI) && __GXX_RTTI) || (defined(_HAS_STATIC_RTTI) && (_HAS_STATIC_RTTI == 0))
+#if CLI11_USE_STATIC_RTTI == 0
         return std::dynamic_pointer_cast<ConfigBase>(config_formatter_);
 #else
         return std::static_pointer_cast<ConfigBase>(config_formatter_);
@@ -1550,7 +990,7 @@ class App {
     }
 
     /// Get the app or subcommand description
-    std::string get_description() const { return description_; }
+    CLI11_NODISCARD std::string get_description() const { return description_; }
 
     /// Set the description of the app
     App *description(std::string app_description) {
@@ -1559,79 +999,20 @@ class App {
     }
 
     /// Get the list of options (user facing function, so returns raw pointers), has optional filter function
-    std::vector<const Option *> get_options(const std::function<bool(const Option *)> filter = {}) const {
-        std::vector<const Option *> options(options_.size());
-        std::transform(std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) {
-            return val.get();
-        });
-
-        if(filter) {
-            options.erase(std::remove_if(std::begin(options),
-                                         std::end(options),
-                                         [&filter](const Option *opt) { return !filter(opt); }),
-                          std::end(options));
-        }
-
-        return options;
-    }
+    std::vector<const Option *> get_options(const std::function<bool(const Option *)> filter = {}) const;
 
     /// Non-const version of the above
-    std::vector<Option *> get_options(const std::function<bool(Option *)> filter = {}) {
-        std::vector<Option *> options(options_.size());
-        std::transform(std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) {
-            return val.get();
-        });
-
-        if(filter) {
-            options.erase(
-                std::remove_if(std::begin(options), std::end(options), [&filter](Option *opt) { return !filter(opt); }),
-                std::end(options));
-        }
-
-        return options;
-    }
+    std::vector<Option *> get_options(const std::function<bool(Option *)> filter = {});
 
     /// Get an option by name (noexcept non-const version)
-    Option *get_option_no_throw(std::string option_name) noexcept {
-        for(Option_p &opt : options_) {
-            if(opt->check_name(option_name)) {
-                return opt.get();
-            }
-        }
-        for(auto &subc : subcommands_) {
-            // also check down into nameless subcommands
-            if(subc->get_name().empty()) {
-                auto opt = subc->get_option_no_throw(option_name);
-                if(opt != nullptr) {
-                    return opt;
-                }
-            }
-        }
-        return nullptr;
-    }
+    Option *get_option_no_throw(std::string option_name) noexcept;
 
     /// Get an option by name (noexcept const version)
-    const Option *get_option_no_throw(std::string option_name) const noexcept {
-        for(const Option_p &opt : options_) {
-            if(opt->check_name(option_name)) {
-                return opt.get();
-            }
-        }
-        for(const auto &subc : subcommands_) {
-            // also check down into nameless subcommands
-            if(subc->get_name().empty()) {
-                auto opt = subc->get_option_no_throw(option_name);
-                if(opt != nullptr) {
-                    return opt;
-                }
-            }
-        }
-        return nullptr;
-    }
+    CLI11_NODISCARD const Option *get_option_no_throw(std::string option_name) const noexcept;
 
     /// Get an option by name
-    const Option *get_option(std::string option_name) const {
-        auto opt = get_option_no_throw(option_name);
+    CLI11_NODISCARD const Option *get_option(std::string option_name) const {
+        const auto *opt = get_option_no_throw(option_name);
         if(opt == nullptr) {
             throw OptionNotFound(option_name);
         }
@@ -1640,7 +1021,7 @@ class App {
 
     /// Get an option by name (non-const version)
     Option *get_option(std::string option_name) {
-        auto opt = get_option_no_throw(option_name);
+        auto *opt = get_option_no_throw(option_name);
         if(opt == nullptr) {
             throw OptionNotFound(option_name);
         }
@@ -1654,102 +1035,106 @@ class App {
     const Option *operator[](const char *option_name) const { return get_option(option_name); }
 
     /// Check the status of ignore_case
-    bool get_ignore_case() const { return ignore_case_; }
+    CLI11_NODISCARD bool get_ignore_case() const { return ignore_case_; }
 
     /// Check the status of ignore_underscore
-    bool get_ignore_underscore() const { return ignore_underscore_; }
+    CLI11_NODISCARD bool get_ignore_underscore() const { return ignore_underscore_; }
 
     /// Check the status of fallthrough
-    bool get_fallthrough() const { return fallthrough_; }
+    CLI11_NODISCARD bool get_fallthrough() const { return fallthrough_; }
 
     /// Check the status of the allow windows style options
-    bool get_allow_windows_style_options() const { return allow_windows_style_options_; }
+    CLI11_NODISCARD bool get_allow_windows_style_options() const { return allow_windows_style_options_; }
 
     /// Check the status of the allow windows style options
-    bool get_positionals_at_end() const { return positionals_at_end_; }
+    CLI11_NODISCARD bool get_positionals_at_end() const { return positionals_at_end_; }
 
     /// Check the status of the allow windows style options
-    bool get_configurable() const { return configurable_; }
+    CLI11_NODISCARD bool get_configurable() const { return configurable_; }
 
     /// Get the group of this subcommand
-    const std::string &get_group() const { return group_; }
+    CLI11_NODISCARD const std::string &get_group() const { return group_; }
 
     /// Generate and return the footer.
-    std::string get_footer() const { return (footer_callback_) ? footer_callback_() + '\n' + footer_ : footer_; }
+    CLI11_NODISCARD std::string get_footer() const {
+        return (footer_callback_) ? footer_callback_() + '\n' + footer_ : footer_;
+    }
 
     /// Get the required min subcommand value
-    std::size_t get_require_subcommand_min() const { return require_subcommand_min_; }
+    CLI11_NODISCARD std::size_t get_require_subcommand_min() const { return require_subcommand_min_; }
 
     /// Get the required max subcommand value
-    std::size_t get_require_subcommand_max() const { return require_subcommand_max_; }
+    CLI11_NODISCARD std::size_t get_require_subcommand_max() const { return require_subcommand_max_; }
 
     /// Get the required min option value
-    std::size_t get_require_option_min() const { return require_option_min_; }
+    CLI11_NODISCARD std::size_t get_require_option_min() const { return require_option_min_; }
 
     /// Get the required max option value
-    std::size_t get_require_option_max() const { return require_option_max_; }
+    CLI11_NODISCARD std::size_t get_require_option_max() const { return require_option_max_; }
 
     /// Get the prefix command status
-    bool get_prefix_command() const { return prefix_command_; }
+    CLI11_NODISCARD bool get_prefix_command() const { return prefix_command_; }
 
     /// Get the status of allow extras
-    bool get_allow_extras() const { return allow_extras_; }
+    CLI11_NODISCARD bool get_allow_extras() const { return allow_extras_; }
 
     /// Get the status of required
-    bool get_required() const { return required_; }
+    CLI11_NODISCARD bool get_required() const { return required_; }
 
     /// Get the status of disabled
-    bool get_disabled() const { return disabled_; }
+    CLI11_NODISCARD bool get_disabled() const { return disabled_; }
 
     /// Get the status of silence
-    bool get_silent() const { return silent_; }
+    CLI11_NODISCARD bool get_silent() const { return silent_; }
 
     /// Get the status of disabled
-    bool get_immediate_callback() const { return immediate_callback_; }
+    CLI11_NODISCARD bool get_immediate_callback() const { return immediate_callback_; }
 
     /// Get the status of disabled by default
-    bool get_disabled_by_default() const { return (default_startup == startup_mode::disabled); }
+    CLI11_NODISCARD bool get_disabled_by_default() const { return (default_startup == startup_mode::disabled); }
 
     /// Get the status of disabled by default
-    bool get_enabled_by_default() const { return (default_startup == startup_mode::enabled); }
+    CLI11_NODISCARD bool get_enabled_by_default() const { return (default_startup == startup_mode::enabled); }
     /// Get the status of validating positionals
-    bool get_validate_positionals() const { return validate_positionals_; }
+    CLI11_NODISCARD bool get_validate_positionals() const { return validate_positionals_; }
+    /// Get the status of validating optional vector arguments
+    CLI11_NODISCARD bool get_validate_optional_arguments() const { return validate_optional_arguments_; }
 
     /// Get the status of allow extras
-    config_extras_mode get_allow_config_extras() const { return allow_config_extras_; }
+    CLI11_NODISCARD config_extras_mode get_allow_config_extras() const { return allow_config_extras_; }
 
     /// Get a pointer to the help flag.
     Option *get_help_ptr() { return help_ptr_; }
 
     /// Get a pointer to the help flag. (const)
-    const Option *get_help_ptr() const { return help_ptr_; }
+    CLI11_NODISCARD const Option *get_help_ptr() const { return help_ptr_; }
 
     /// Get a pointer to the help all flag. (const)
-    const Option *get_help_all_ptr() const { return help_all_ptr_; }
+    CLI11_NODISCARD const Option *get_help_all_ptr() const { return help_all_ptr_; }
 
     /// Get a pointer to the config option.
     Option *get_config_ptr() { return config_ptr_; }
 
     /// Get a pointer to the config option. (const)
-    const Option *get_config_ptr() const { return config_ptr_; }
+    CLI11_NODISCARD const Option *get_config_ptr() const { return config_ptr_; }
 
     /// Get a pointer to the version option.
     Option *get_version_ptr() { return version_ptr_; }
 
     /// Get a pointer to the version option. (const)
-    const Option *get_version_ptr() const { return version_ptr_; }
+    CLI11_NODISCARD const Option *get_version_ptr() const { return version_ptr_; }
 
     /// Get the parent of this subcommand (or nullptr if main app)
     App *get_parent() { return parent_; }
 
     /// Get the parent of this subcommand (or nullptr if main app) (const version)
-    const App *get_parent() const { return parent_; }
+    CLI11_NODISCARD const App *get_parent() const { return parent_; }
 
     /// Get the name of the current app
-    const std::string &get_name() const { return name_; }
+    CLI11_NODISCARD const std::string &get_name() const { return name_; }
 
     /// Get the aliases of the current app
-    const std::vector<std::string> &get_aliases() const { return aliases_; }
+    CLI11_NODISCARD const std::vector<std::string> &get_aliases() const { return aliases_; }
 
     /// clear all the aliases of the current App
     App *clear_aliases() {
@@ -1758,116 +1143,25 @@ class App {
     }
 
     /// Get a display name for an app
-    std::string get_display_name(bool with_aliases = false) const {
-        if(name_.empty()) {
-            return std::string("[Option Group: ") + get_group() + "]";
-        }
-        if(aliases_.empty() || !with_aliases) {
-            return name_;
-        }
-        std::string dispname = name_;
-        for(const auto &lalias : aliases_) {
-            dispname.push_back(',');
-            dispname.push_back(' ');
-            dispname.append(lalias);
-        }
-        return dispname;
-    }
+    CLI11_NODISCARD std::string get_display_name(bool with_aliases = false) const;
 
     /// Check the name, case insensitive and underscore insensitive if set
-    bool check_name(std::string name_to_check) const {
-        std::string local_name = name_;
-        if(ignore_underscore_) {
-            local_name = detail::remove_underscore(name_);
-            name_to_check = detail::remove_underscore(name_to_check);
-        }
-        if(ignore_case_) {
-            local_name = detail::to_lower(name_);
-            name_to_check = detail::to_lower(name_to_check);
-        }
-
-        if(local_name == name_to_check) {
-            return true;
-        }
-        for(auto les : aliases_) {
-            if(ignore_underscore_) {
-                les = detail::remove_underscore(les);
-            }
-            if(ignore_case_) {
-                les = detail::to_lower(les);
-            }
-            if(les == name_to_check) {
-                return true;
-            }
-        }
-        return false;
-    }
+    CLI11_NODISCARD bool check_name(std::string name_to_check) const;
 
     /// Get the groups available directly from this option (in order)
-    std::vector<std::string> get_groups() const {
-        std::vector<std::string> groups;
-
-        for(const Option_p &opt : options_) {
-            // Add group if it is not already in there
-            if(std::find(groups.begin(), groups.end(), opt->get_group()) == groups.end()) {
-                groups.push_back(opt->get_group());
-            }
-        }
-
-        return groups;
-    }
+    CLI11_NODISCARD std::vector<std::string> get_groups() const;
 
     /// This gets a vector of pointers with the original parse order
-    const std::vector<Option *> &parse_order() const { return parse_order_; }
+    CLI11_NODISCARD const std::vector<Option *> &parse_order() const { return parse_order_; }
 
     /// This returns the missing options from the current subcommand
-    std::vector<std::string> remaining(bool recurse = false) const {
-        std::vector<std::string> miss_list;
-        for(const std::pair<detail::Classifier, std::string> &miss : missing_) {
-            miss_list.push_back(std::get<1>(miss));
-        }
-        // Get from a subcommand that may allow extras
-        if(recurse) {
-            if(!allow_extras_) {
-                for(const auto &sub : subcommands_) {
-                    if(sub->name_.empty() && !sub->missing_.empty()) {
-                        for(const std::pair<detail::Classifier, std::string> &miss : sub->missing_) {
-                            miss_list.push_back(std::get<1>(miss));
-                        }
-                    }
-                }
-            }
-            // Recurse into subcommands
-
-            for(const App *sub : parsed_subcommands_) {
-                std::vector<std::string> output = sub->remaining(recurse);
-                std::copy(std::begin(output), std::end(output), std::back_inserter(miss_list));
-            }
-        }
-        return miss_list;
-    }
+    CLI11_NODISCARD std::vector<std::string> remaining(bool recurse = false) const;
 
     /// This returns the missing options in a form ready for processing by another command line program
-    std::vector<std::string> remaining_for_passthrough(bool recurse = false) const {
-        std::vector<std::string> miss_list = remaining(recurse);
-        std::reverse(std::begin(miss_list), std::end(miss_list));
-        return miss_list;
-    }
+    CLI11_NODISCARD std::vector<std::string> remaining_for_passthrough(bool recurse = false) const;
 
     /// This returns the number of remaining options, minus the -- separator
-    std::size_t remaining_size(bool recurse = false) const {
-        auto remaining_options = static_cast<std::size_t>(std::count_if(
-            std::begin(missing_), std::end(missing_), [](const std::pair<detail::Classifier, std::string> &val) {
-                return val.first != detail::Classifier::POSITIONAL_MARK;
-            }));
-
-        if(recurse) {
-            for(const App_p &sub : subcommands_) {
-                remaining_options += sub->remaining_size(recurse);
-            }
-        }
-        return remaining_options;
-    }
+    CLI11_NODISCARD std::size_t remaining_size(bool recurse = false) const;
 
     ///@}
 
@@ -1876,1107 +1170,118 @@ class App {
     ///
     /// Currently checks to see if multiple positionals exist with unlimited args and checks if the min and max options
     /// are feasible
-    void _validate() const {
-        // count the number of positional only args
-        auto pcount = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) {
-            return opt->get_items_expected_max() >= detail::expected_max_vector_size && !opt->nonpositional();
-        });
-        if(pcount > 1) {
-            auto pcount_req = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) {
-                return opt->get_items_expected_max() >= detail::expected_max_vector_size && !opt->nonpositional() &&
-                       opt->get_required();
-            });
-            if(pcount - pcount_req > 1) {
-                throw InvalidError(name_);
-            }
-        }
-
-        std::size_t nameless_subs{0};
-        for(const App_p &app : subcommands_) {
-            app->_validate();
-            if(app->get_name().empty())
-                ++nameless_subs;
-        }
-
-        if(require_option_min_ > 0) {
-            if(require_option_max_ > 0) {
-                if(require_option_max_ < require_option_min_) {
-                    throw(InvalidError("Required min options greater than required max options",
-                                       ExitCodes::InvalidError));
-                }
-            }
-            if(require_option_min_ > (options_.size() + nameless_subs)) {
-                throw(InvalidError("Required min options greater than number of available options",
-                                   ExitCodes::InvalidError));
-            }
-        }
-    }
+    void _validate() const;
 
     /// configure subcommands to enable parsing through the current object
     /// set the correct fallthrough and prefix for nameless subcommands and manage the automatic enable or disable
     /// makes sure parent is set correctly
-    void _configure() {
-        if(default_startup == startup_mode::enabled) {
-            disabled_ = false;
-        } else if(default_startup == startup_mode::disabled) {
-            disabled_ = true;
-        }
-        for(const App_p &app : subcommands_) {
-            if(app->has_automatic_name_) {
-                app->name_.clear();
-            }
-            if(app->name_.empty()) {
-                app->fallthrough_ = false;  // make sure fallthrough_ is false to prevent infinite loop
-                app->prefix_command_ = false;
-            }
-            // make sure the parent is set to be this object in preparation for parse
-            app->parent_ = this;
-            app->_configure();
-        }
-    }
+    void _configure();
 
     /// Internal function to run (App) callback, bottom up
-    void run_callback(bool final_mode = false, bool suppress_final_callback = false) {
-        pre_callback();
-        // in the main app if immediate_callback_ is set it runs the main callback before the used subcommands
-        if(!final_mode && parse_complete_callback_) {
-            parse_complete_callback_();
-        }
-        // run the callbacks for the received subcommands
-        for(App *subc : get_subcommands()) {
-            subc->run_callback(true, suppress_final_callback);
-        }
-        // now run callbacks for option_groups
-        for(auto &subc : subcommands_) {
-            if(subc->name_.empty() && subc->count_all() > 0) {
-                subc->run_callback(true, suppress_final_callback);
-            }
-        }
-
-        // finally run the main callback
-        if(final_callback_ && (parsed_ > 0) && (!suppress_final_callback)) {
-            if(!name_.empty() || count_all() > 0 || parent_ == nullptr) {
-                final_callback_();
-            }
-        }
-    }
+    void run_callback(bool final_mode = false, bool suppress_final_callback = false);
 
     /// Check to see if a subcommand is valid. Give up immediately if subcommand max has been reached.
-    bool _valid_subcommand(const std::string &current, bool ignore_used = true) const {
-        // Don't match if max has been reached - but still check parents
-        if(require_subcommand_max_ != 0 && parsed_subcommands_.size() >= require_subcommand_max_) {
-            return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used);
-        }
-        auto com = _find_subcommand(current, true, ignore_used);
-        if(com != nullptr) {
-            return true;
-        }
-        // Check parent if exists, else return false
-        return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used);
-    }
+    CLI11_NODISCARD bool _valid_subcommand(const std::string &current, bool ignore_used = true) const;
 
     /// Selects a Classifier enum based on the type of the current argument
-    detail::Classifier _recognize(const std::string &current, bool ignore_used_subcommands = true) const {
-        std::string dummy1, dummy2;
-
-        if(current == "--")
-            return detail::Classifier::POSITIONAL_MARK;
-        if(_valid_subcommand(current, ignore_used_subcommands))
-            return detail::Classifier::SUBCOMMAND;
-        if(detail::split_long(current, dummy1, dummy2))
-            return detail::Classifier::LONG;
-        if(detail::split_short(current, dummy1, dummy2)) {
-            if(dummy1[0] >= '0' && dummy1[0] <= '9') {
-                if(get_option_no_throw(std::string{'-', dummy1[0]}) == nullptr) {
-                    return detail::Classifier::NONE;
-                }
-            }
-            return detail::Classifier::SHORT;
-        }
-        if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2)))
-            return detail::Classifier::WINDOWS_STYLE;
-        if((current == "++") && !name_.empty() && parent_ != nullptr)
-            return detail::Classifier::SUBCOMMAND_TERMINATOR;
-        return detail::Classifier::NONE;
-    }
+    CLI11_NODISCARD detail::Classifier _recognize(const std::string &current,
+                                                  bool ignore_used_subcommands = true) const;
 
     // The parse function is now broken into several parts, and part of process
 
     /// Read and process a configuration file (main app only)
-    void _process_config_file() {
-        if(config_ptr_ != nullptr) {
-            bool config_required = config_ptr_->get_required();
-            auto file_given = config_ptr_->count() > 0;
-            auto config_files = config_ptr_->as<std::vector<std::string>>();
-            if(config_files.empty() || config_files.front().empty()) {
-                if(config_required) {
-                    throw FileError::Missing("no specified config file");
-                }
-                return;
-            }
-            for(auto rit = config_files.rbegin(); rit != config_files.rend(); ++rit) {
-                const auto &config_file = *rit;
-                auto path_result = detail::check_path(config_file.c_str());
-                if(path_result == detail::path_type::file) {
-                    try {
-                        std::vector<ConfigItem> values = config_formatter_->from_file(config_file);
-                        _parse_config(values);
-                        if(!file_given) {
-                            config_ptr_->add_result(config_file);
-                        }
-                    } catch(const FileError &) {
-                        if(config_required || file_given)
-                            throw;
-                    }
-                } else if(config_required || file_given) {
-                    throw FileError::Missing(config_file);
-                }
-            }
-        }
-    }
+    void _process_config_file();
 
     /// Get envname options if not yet passed. Runs on *all* subcommands.
-    void _process_env() {
-        for(const Option_p &opt : options_) {
-            if(opt->count() == 0 && !opt->envname_.empty()) {
-                char *buffer = nullptr;
-                std::string ename_string;
-
-#ifdef _MSC_VER
-                // Windows version
-                std::size_t sz = 0;
-                if(_dupenv_s(&buffer, &sz, opt->envname_.c_str()) == 0 && buffer != nullptr) {
-                    ename_string = std::string(buffer);
-                    free(buffer);
-                }
-#else
-                // This also works on Windows, but gives a warning
-                buffer = std::getenv(opt->envname_.c_str());
-                if(buffer != nullptr)
-                    ename_string = std::string(buffer);
-#endif
-
-                if(!ename_string.empty()) {
-                    opt->add_result(ename_string);
-                }
-            }
-        }
-
-        for(App_p &sub : subcommands_) {
-            if(sub->get_name().empty() || !sub->parse_complete_callback_)
-                sub->_process_env();
-        }
-    }
+    void _process_env();
 
     /// Process callbacks. Runs on *all* subcommands.
-    void _process_callbacks() {
-
-        for(App_p &sub : subcommands_) {
-            // process the priority option_groups first
-            if(sub->get_name().empty() && sub->parse_complete_callback_) {
-                if(sub->count_all() > 0) {
-                    sub->_process_callbacks();
-                    sub->run_callback();
-                }
-            }
-        }
-
-        for(const Option_p &opt : options_) {
-            if((*opt) && !opt->get_callback_run()) {
-                opt->run_callback();
-            }
-        }
-        for(App_p &sub : subcommands_) {
-            if(!sub->parse_complete_callback_) {
-                sub->_process_callbacks();
-            }
-        }
-    }
+    void _process_callbacks();
 
     /// Run help flag processing if any are found.
     ///
     /// The flags allow recursive calls to remember if there was a help flag on a parent.
-    void _process_help_flags(bool trigger_help = false, bool trigger_all_help = false) const {
-        const Option *help_ptr = get_help_ptr();
-        const Option *help_all_ptr = get_help_all_ptr();
-
-        if(help_ptr != nullptr && help_ptr->count() > 0)
-            trigger_help = true;
-        if(help_all_ptr != nullptr && help_all_ptr->count() > 0)
-            trigger_all_help = true;
-
-        // If there were parsed subcommands, call those. First subcommand wins if there are multiple ones.
-        if(!parsed_subcommands_.empty()) {
-            for(const App *sub : parsed_subcommands_)
-                sub->_process_help_flags(trigger_help, trigger_all_help);
-
-            // Only the final subcommand should call for help. All help wins over help.
-        } else if(trigger_all_help) {
-            throw CallForAllHelp();
-        } else if(trigger_help) {
-            throw CallForHelp();
-        }
-    }
+    void _process_help_flags(bool trigger_help = false, bool trigger_all_help = false) const;
 
     /// Verify required options and cross requirements. Subcommands too (only if selected).
-    void _process_requirements() {
-        // check excludes
-        bool excluded{false};
-        std::string excluder;
-        for(auto &opt : exclude_options_) {
-            if(opt->count() > 0) {
-                excluded = true;
-                excluder = opt->get_name();
-            }
-        }
-        for(auto &subc : exclude_subcommands_) {
-            if(subc->count_all() > 0) {
-                excluded = true;
-                excluder = subc->get_display_name();
-            }
-        }
-        if(excluded) {
-            if(count_all() > 0) {
-                throw ExcludesError(get_display_name(), excluder);
-            }
-            // if we are excluded but didn't receive anything, just return
-            return;
-        }
-
-        // check excludes
-        bool missing_needed{false};
-        std::string missing_need;
-        for(auto &opt : need_options_) {
-            if(opt->count() == 0) {
-                missing_needed = true;
-                missing_need = opt->get_name();
-            }
-        }
-        for(auto &subc : need_subcommands_) {
-            if(subc->count_all() == 0) {
-                missing_needed = true;
-                missing_need = subc->get_display_name();
-            }
-        }
-        if(missing_needed) {
-            if(count_all() > 0) {
-                throw RequiresError(get_display_name(), missing_need);
-            }
-            // if we missing something but didn't have any options, just return
-            return;
-        }
-
-        std::size_t used_options = 0;
-        for(const Option_p &opt : options_) {
-
-            if(opt->count() != 0) {
-                ++used_options;
-            }
-            // Required but empty
-            if(opt->get_required() && opt->count() == 0) {
-                throw RequiredError(opt->get_name());
-            }
-            // Requires
-            for(const Option *opt_req : opt->needs_)
-                if(opt->count() > 0 && opt_req->count() == 0)
-                    throw RequiresError(opt->get_name(), opt_req->get_name());
-            // Excludes
-            for(const Option *opt_ex : opt->excludes_)
-                if(opt->count() > 0 && opt_ex->count() != 0)
-                    throw ExcludesError(opt->get_name(), opt_ex->get_name());
-        }
-        // check for the required number of subcommands
-        if(require_subcommand_min_ > 0) {
-            auto selected_subcommands = get_subcommands();
-            if(require_subcommand_min_ > selected_subcommands.size())
-                throw RequiredError::Subcommand(require_subcommand_min_);
-        }
-
-        // Max error cannot occur, the extra subcommand will parse as an ExtrasError or a remaining item.
-
-        // run this loop to check how many unnamed subcommands were actually used since they are considered options
-        // from the perspective of an App
-        for(App_p &sub : subcommands_) {
-            if(sub->disabled_)
-                continue;
-            if(sub->name_.empty() && sub->count_all() > 0) {
-                ++used_options;
-            }
-        }
-
-        if(require_option_min_ > used_options || (require_option_max_ > 0 && require_option_max_ < used_options)) {
-            auto option_list = detail::join(options_, [this](const Option_p &ptr) {
-                if(ptr.get() == help_ptr_ || ptr.get() == help_all_ptr_) {
-                    return std::string{};
-                }
-                return ptr->get_name(false, true);
-            });
-
-            auto subc_list = get_subcommands([](App *app) { return ((app->get_name().empty()) && (!app->disabled_)); });
-            if(!subc_list.empty()) {
-                option_list += "," + detail::join(subc_list, [](const App *app) { return app->get_display_name(); });
-            }
-            throw RequiredError::Option(require_option_min_, require_option_max_, used_options, option_list);
-        }
-
-        // now process the requirements for subcommands if needed
-        for(App_p &sub : subcommands_) {
-            if(sub->disabled_)
-                continue;
-            if(sub->name_.empty() && sub->required_ == false) {
-                if(sub->count_all() == 0) {
-                    if(require_option_min_ > 0 && require_option_min_ <= used_options) {
-                        continue;
-                        // if we have met the requirement and there is nothing in this option group skip checking
-                        // requirements
-                    }
-                    if(require_option_max_ > 0 && used_options >= require_option_min_) {
-                        continue;
-                        // if we have met the requirement and there is nothing in this option group skip checking
-                        // requirements
-                    }
-                }
-            }
-            if(sub->count() > 0 || sub->name_.empty()) {
-                sub->_process_requirements();
-            }
-
-            if(sub->required_ && sub->count_all() == 0) {
-                throw(CLI::RequiredError(sub->get_display_name()));
-            }
-        }
-    }
+    void _process_requirements();
 
     /// Process callbacks and such.
-    void _process() {
-        CLI::FileError fe("ne");
-        bool caught_error{false};
-        try {
-            // the config file might generate a FileError but that should not be processed until later in the process
-            // to allow for help, version and other errors to generate first.
-            _process_config_file();
-            // process env shouldn't throw but no reason to process it if config generated an error
-            _process_env();
-        } catch(const CLI::FileError &fe2) {
-            fe = fe2;
-            caught_error = true;
-        }
-        // callbacks and help_flags can generate exceptions which should take priority over the config file error if one
-        // exists
-        _process_callbacks();
-        _process_help_flags();
-
-        if(caught_error) {
-            throw CLI::FileError(std::move(fe));
-        }
-
-        _process_requirements();
-    }
+    void _process();
 
     /// Throw an error if anything is left over and should not be.
-    void _process_extras() {
-        if(!(allow_extras_ || prefix_command_)) {
-            std::size_t num_left_over = remaining_size();
-            if(num_left_over > 0) {
-                throw ExtrasError(name_, remaining(false));
-            }
-        }
-
-        for(App_p &sub : subcommands_) {
-            if(sub->count() > 0)
-                sub->_process_extras();
-        }
-    }
+    void _process_extras();
 
     /// Throw an error if anything is left over and should not be.
     /// Modifies the args to fill in the missing items before throwing.
-    void _process_extras(std::vector<std::string> &args) {
-        if(!(allow_extras_ || prefix_command_)) {
-            std::size_t num_left_over = remaining_size();
-            if(num_left_over > 0) {
-                args = remaining(false);
-                throw ExtrasError(name_, args);
-            }
-        }
-
-        for(App_p &sub : subcommands_) {
-            if(sub->count() > 0)
-                sub->_process_extras(args);
-        }
-    }
+    void _process_extras(std::vector<std::string> &args);
 
     /// Internal function to recursively increment the parsed counter on the current app as well unnamed subcommands
-    void increment_parsed() {
-        ++parsed_;
-        for(App_p &sub : subcommands_) {
-            if(sub->get_name().empty())
-                sub->increment_parsed();
-        }
-    }
-    /// Internal parse function
-    void _parse(std::vector<std::string> &args) {
-        increment_parsed();
-        _trigger_pre_parse(args.size());
-        bool positional_only = false;
-
-        while(!args.empty()) {
-            if(!_parse_single(args, positional_only)) {
-                break;
-            }
-        }
-
-        if(parent_ == nullptr) {
-            _process();
-
-            // Throw error if any items are left over (depending on settings)
-            _process_extras(args);
-
-            // Convert missing (pairs) to extras (string only) ready for processing in another app
-            args = remaining_for_passthrough(false);
-        } else if(parse_complete_callback_) {
-            _process_env();
-            _process_callbacks();
-            _process_help_flags();
-            _process_requirements();
-            run_callback(false, true);
-        }
-    }
+    void increment_parsed();
 
     /// Internal parse function
-    void _parse(std::vector<std::string> &&args) {
-        // this can only be called by the top level in which case parent == nullptr by definition
-        // operation is simplified
-        increment_parsed();
-        _trigger_pre_parse(args.size());
-        bool positional_only = false;
-
-        while(!args.empty()) {
-            _parse_single(args, positional_only);
-        }
-        _process();
+    void _parse(std::vector<std::string> &args);
 
-        // Throw error if any items are left over (depending on settings)
-        _process_extras();
-    }
+    /// Internal parse function
+    void _parse(std::vector<std::string> &&args);
 
     /// Internal function to parse a stream
-    void _parse_stream(std::istream &input) {
-        auto values = config_formatter_->from_config(input);
-        _parse_config(values);
-        increment_parsed();
-        _trigger_pre_parse(values.size());
-        _process();
-
-        // Throw error if any items are left over (depending on settings)
-        _process_extras();
-    }
+    void _parse_stream(std::istream &input);
 
     /// Parse one config param, return false if not found in any subcommand, remove if it is
     ///
     /// If this has more than one dot.separated.name, go into the subcommand matching it
     /// Returns true if it managed to find the option, if false you'll need to remove the arg manually.
-    void _parse_config(const std::vector<ConfigItem> &args) {
-        for(const ConfigItem &item : args) {
-            if(!_parse_single_config(item) && allow_config_extras_ == config_extras_mode::error)
-                throw ConfigError::Extras(item.fullname());
-        }
-    }
+    void _parse_config(const std::vector<ConfigItem> &args);
 
     /// Fill in a single config option
-    bool _parse_single_config(const ConfigItem &item, std::size_t level = 0) {
-        if(level < item.parents.size()) {
-            try {
-                auto subcom = get_subcommand(item.parents.at(level));
-                auto result = subcom->_parse_single_config(item, level + 1);
-
-                return result;
-            } catch(const OptionNotFound &) {
-                return false;
-            }
-        }
-        // check for section open
-        if(item.name == "++") {
-            if(configurable_) {
-                increment_parsed();
-                _trigger_pre_parse(2);
-                if(parent_ != nullptr) {
-                    parent_->parsed_subcommands_.push_back(this);
-                }
-            }
-            return true;
-        }
-        // check for section close
-        if(item.name == "--") {
-            if(configurable_) {
-                _process_callbacks();
-                _process_requirements();
-                run_callback();
-            }
-            return true;
-        }
-        Option *op = get_option_no_throw("--" + item.name);
-        if(op == nullptr) {
-            if(item.name.size() == 1) {
-                op = get_option_no_throw("-" + item.name);
-            }
-        }
-        if(op == nullptr) {
-            op = get_option_no_throw(item.name);
-        }
-        if(op == nullptr) {
-            // If the option was not present
-            if(get_allow_config_extras() == config_extras_mode::capture)
-                // Should we worry about classifying the extras properly?
-                missing_.emplace_back(detail::Classifier::NONE, item.fullname());
-            return false;
-        }
-
-        if(!op->get_configurable()) {
-            if(get_allow_config_extras() == config_extras_mode::ignore_all) {
-                return false;
-            }
-            throw ConfigError::NotConfigurable(item.fullname());
-        }
-
-        if(op->empty()) {
-            // Flag parsing
-            if(op->get_expected_min() == 0) {
-                auto res = config_formatter_->to_flag(item);
-                res = op->get_flag_value(item.name, res);
-
-                op->add_result(res);
-
-            } else {
-                op->add_result(item.inputs);
-                op->run_callback();
-            }
-        }
-
-        return true;
-    }
+    bool _parse_single_config(const ConfigItem &item, std::size_t level = 0);
 
     /// Parse "one" argument (some may eat more than one), delegate to parent if fails, add to missing if missing
     /// from main return false if the parse has failed and needs to return to parent
-    bool _parse_single(std::vector<std::string> &args, bool &positional_only) {
-        bool retval = true;
-        detail::Classifier classifier = positional_only ? detail::Classifier::NONE : _recognize(args.back());
-        switch(classifier) {
-        case detail::Classifier::POSITIONAL_MARK:
-            args.pop_back();
-            positional_only = true;
-            if((!_has_remaining_positionals()) && (parent_ != nullptr)) {
-                retval = false;
-            } else {
-                _move_to_missing(classifier, "--");
-            }
-            break;
-        case detail::Classifier::SUBCOMMAND_TERMINATOR:
-            // treat this like a positional mark if in the parent app
-            args.pop_back();
-            retval = false;
-            break;
-        case detail::Classifier::SUBCOMMAND:
-            retval = _parse_subcommand(args);
-            break;
-        case detail::Classifier::LONG:
-        case detail::Classifier::SHORT:
-        case detail::Classifier::WINDOWS_STYLE:
-            // If already parsed a subcommand, don't accept options_
-            _parse_arg(args, classifier);
-            break;
-        case detail::Classifier::NONE:
-            // Probably a positional or something for a parent (sub)command
-            retval = _parse_positional(args, false);
-            if(retval && positionals_at_end_) {
-                positional_only = true;
-            }
-            break;
-            // LCOV_EXCL_START
-        default:
-            throw HorribleError("unrecognized classifier (you should not see this!)");
-            // LCOV_EXCL_STOP
-        }
-        return retval;
-    }
+    bool _parse_single(std::vector<std::string> &args, bool &positional_only);
 
     /// Count the required remaining positional arguments
-    std::size_t _count_remaining_positionals(bool required_only = false) const {
-        std::size_t retval = 0;
-        for(const Option_p &opt : options_) {
-            if(opt->get_positional() && (!required_only || opt->get_required())) {
-                if(opt->get_items_expected_min() > 0 &&
-                   static_cast<int>(opt->count()) < opt->get_items_expected_min()) {
-                    retval += static_cast<std::size_t>(opt->get_items_expected_min()) - opt->count();
-                }
-            }
-        }
-        return retval;
-    }
+    CLI11_NODISCARD std::size_t _count_remaining_positionals(bool required_only = false) const;
 
     /// Count the required remaining positional arguments
-    bool _has_remaining_positionals() const {
-        for(const Option_p &opt : options_) {
-            if(opt->get_positional() && ((static_cast<int>(opt->count()) < opt->get_items_expected_min()))) {
-                return true;
-            }
-        }
-
-        return false;
-    }
+    CLI11_NODISCARD bool _has_remaining_positionals() const;
 
     /// Parse a positional, go up the tree to check
     /// @param haltOnSubcommand if set to true the operation will not process subcommands merely return false
     /// Return true if the positional was used false otherwise
-    bool _parse_positional(std::vector<std::string> &args, bool haltOnSubcommand) {
-
-        const std::string &positional = args.back();
-
-        if(positionals_at_end_) {
-            // deal with the case of required arguments at the end which should take precedence over other arguments
-            auto arg_rem = args.size();
-            auto remreq = _count_remaining_positionals(true);
-            if(arg_rem <= remreq) {
-                for(const Option_p &opt : options_) {
-                    if(opt->get_positional() && opt->required_) {
-                        if(static_cast<int>(opt->count()) < opt->get_items_expected_min()) {
-                            if(validate_positionals_) {
-                                std::string pos = positional;
-                                pos = opt->_validate(pos, 0);
-                                if(!pos.empty()) {
-                                    continue;
-                                }
-                            }
-                            opt->add_result(positional);
-                            parse_order_.push_back(opt.get());
-                            args.pop_back();
-                            return true;
-                        }
-                    }
-                }
-            }
-        }
-        for(const Option_p &opt : options_) {
-            // Eat options, one by one, until done
-            if(opt->get_positional() &&
-               (static_cast<int>(opt->count()) < opt->get_items_expected_min() || opt->get_allow_extra_args())) {
-                if(validate_positionals_) {
-                    std::string pos = positional;
-                    pos = opt->_validate(pos, 0);
-                    if(!pos.empty()) {
-                        continue;
-                    }
-                }
-                opt->add_result(positional);
-                parse_order_.push_back(opt.get());
-                args.pop_back();
-                return true;
-            }
-        }
-
-        for(auto &subc : subcommands_) {
-            if((subc->name_.empty()) && (!subc->disabled_)) {
-                if(subc->_parse_positional(args, false)) {
-                    if(!subc->pre_parse_called_) {
-                        subc->_trigger_pre_parse(args.size());
-                    }
-                    return true;
-                }
-            }
-        }
-        // let the parent deal with it if possible
-        if(parent_ != nullptr && fallthrough_)
-            return _get_fallthrough_parent()->_parse_positional(args, static_cast<bool>(parse_complete_callback_));
-
-        /// Try to find a local subcommand that is repeated
-        auto com = _find_subcommand(args.back(), true, false);
-        if(com != nullptr && (require_subcommand_max_ == 0 || require_subcommand_max_ > parsed_subcommands_.size())) {
-            if(haltOnSubcommand) {
-                return false;
-            }
-            args.pop_back();
-            com->_parse(args);
-            return true;
-        }
-        /// now try one last gasp at subcommands that have been executed before, go to root app and try to find a
-        /// subcommand in a broader way, if one exists let the parent deal with it
-        auto parent_app = (parent_ != nullptr) ? _get_fallthrough_parent() : this;
-        com = parent_app->_find_subcommand(args.back(), true, false);
-        if(com != nullptr && (com->parent_->require_subcommand_max_ == 0 ||
-                              com->parent_->require_subcommand_max_ > com->parent_->parsed_subcommands_.size())) {
-            return false;
-        }
-
-        if(positionals_at_end_) {
-            throw CLI::ExtrasError(name_, args);
-        }
-        /// If this is an option group don't deal with it
-        if(parent_ != nullptr && name_.empty()) {
-            return false;
-        }
-        /// We are out of other options this goes to missing
-        _move_to_missing(detail::Classifier::NONE, positional);
-        args.pop_back();
-        if(prefix_command_) {
-            while(!args.empty()) {
-                _move_to_missing(detail::Classifier::NONE, args.back());
-                args.pop_back();
-            }
-        }
-
-        return true;
-    }
+    bool _parse_positional(std::vector<std::string> &args, bool haltOnSubcommand);
 
     /// Locate a subcommand by name with two conditions, should disabled subcommands be ignored, and should used
     /// subcommands be ignored
-    App *_find_subcommand(const std::string &subc_name, bool ignore_disabled, bool ignore_used) const noexcept {
-        for(const App_p &com : subcommands_) {
-            if(com->disabled_ && ignore_disabled)
-                continue;
-            if(com->get_name().empty()) {
-                auto subc = com->_find_subcommand(subc_name, ignore_disabled, ignore_used);
-                if(subc != nullptr) {
-                    return subc;
-                }
-            }
-            if(com->check_name(subc_name)) {
-                if((!*com) || !ignore_used)
-                    return com.get();
-            }
-        }
-        return nullptr;
-    }
+    CLI11_NODISCARD App *
+    _find_subcommand(const std::string &subc_name, bool ignore_disabled, bool ignore_used) const noexcept;
 
     /// Parse a subcommand, modify args and continue
     ///
     /// Unlike the others, this one will always allow fallthrough
     /// return true if the subcommand was processed false otherwise
-    bool _parse_subcommand(std::vector<std::string> &args) {
-        if(_count_remaining_positionals(/* required */ true) > 0) {
-            _parse_positional(args, false);
-            return true;
-        }
-        auto com = _find_subcommand(args.back(), true, true);
-        if(com != nullptr) {
-            args.pop_back();
-            if(!com->silent_) {
-                parsed_subcommands_.push_back(com);
-            }
-            com->_parse(args);
-            auto parent_app = com->parent_;
-            while(parent_app != this) {
-                parent_app->_trigger_pre_parse(args.size());
-                if(!com->silent_) {
-                    parent_app->parsed_subcommands_.push_back(com);
-                }
-                parent_app = parent_app->parent_;
-            }
-            return true;
-        }
-
-        if(parent_ == nullptr)
-            throw HorribleError("Subcommand " + args.back() + " missing");
-        return false;
-    }
+    bool _parse_subcommand(std::vector<std::string> &args);
 
     /// Parse a short (false) or long (true) argument, must be at the top of the list
     /// return true if the argument was processed or false if nothing was done
-    bool _parse_arg(std::vector<std::string> &args, detail::Classifier current_type) {
-
-        std::string current = args.back();
-
-        std::string arg_name;
-        std::string value;
-        std::string rest;
-
-        switch(current_type) {
-        case detail::Classifier::LONG:
-            if(!detail::split_long(current, arg_name, value))
-                throw HorribleError("Long parsed but missing (you should not see this):" + args.back());
-            break;
-        case detail::Classifier::SHORT:
-            if(!detail::split_short(current, arg_name, rest))
-                throw HorribleError("Short parsed but missing! You should not see this");
-            break;
-        case detail::Classifier::WINDOWS_STYLE:
-            if(!detail::split_windows_style(current, arg_name, value))
-                throw HorribleError("windows option parsed but missing! You should not see this");
-            break;
-        case detail::Classifier::SUBCOMMAND:
-        case detail::Classifier::SUBCOMMAND_TERMINATOR:
-        case detail::Classifier::POSITIONAL_MARK:
-        case detail::Classifier::NONE:
-        default:
-            throw HorribleError("parsing got called with invalid option! You should not see this");
-        }
-
-        auto op_ptr =
-            std::find_if(std::begin(options_), std::end(options_), [arg_name, current_type](const Option_p &opt) {
-                if(current_type == detail::Classifier::LONG)
-                    return opt->check_lname(arg_name);
-                if(current_type == detail::Classifier::SHORT)
-                    return opt->check_sname(arg_name);
-                // this will only get called for detail::Classifier::WINDOWS_STYLE
-                return opt->check_lname(arg_name) || opt->check_sname(arg_name);
-            });
-
-        // Option not found
-        if(op_ptr == std::end(options_)) {
-            for(auto &subc : subcommands_) {
-                if(subc->name_.empty() && !subc->disabled_) {
-                    if(subc->_parse_arg(args, current_type)) {
-                        if(!subc->pre_parse_called_) {
-                            subc->_trigger_pre_parse(args.size());
-                        }
-                        return true;
-                    }
-                }
-            }
-            // If a subcommand, try the main command
-            if(parent_ != nullptr && fallthrough_)
-                return _get_fallthrough_parent()->_parse_arg(args, current_type);
-            // don't capture missing if this is a nameless subcommand
-            if(parent_ != nullptr && name_.empty()) {
-                return false;
-            }
-            // Otherwise, add to missing
-            args.pop_back();
-            _move_to_missing(current_type, current);
-            return true;
-        }
-
-        args.pop_back();
-
-        // Get a reference to the pointer to make syntax bearable
-        Option_p &op = *op_ptr;
-        /// if we require a separator add it here
-        if(op->get_inject_separator()) {
-            if(!op->results().empty() && !op->results().back().empty()) {
-                op->add_result(std::string{});
-            }
-        }
-        if(op->get_trigger_on_parse() && op->current_option_state_ == Option::option_state::callback_run) {
-            op->clear();
-        }
-        int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min());
-        int max_num = op->get_items_expected_max();
-        // check container like options to limit the argument size to a single type if the allow_extra_flags argument is
-        // set. 16 is somewhat arbitrary (needs to be at least 4)
-        if(max_num >= detail::expected_max_vector_size / 16 && !op->get_allow_extra_args()) {
-            auto tmax = op->get_type_size_max();
-            max_num = detail::checked_multiply(tmax, op->get_expected_min()) ? tmax : detail::expected_max_vector_size;
-        }
-        // Make sure we always eat the minimum for unlimited vectors
-        int collected = 0;     // total number of arguments collected
-        int result_count = 0;  // local variable for number of results in a single arg string
-        // deal with purely flag like things
-        if(max_num == 0) {
-            auto res = op->get_flag_value(arg_name, value);
-            op->add_result(res);
-            parse_order_.push_back(op.get());
-        } else if(!value.empty()) {  // --this=value
-            op->add_result(value, result_count);
-            parse_order_.push_back(op.get());
-            collected += result_count;
-            // -Trest
-        } else if(!rest.empty()) {
-            op->add_result(rest, result_count);
-            parse_order_.push_back(op.get());
-            rest = "";
-            collected += result_count;
-        }
-
-        // gather the minimum number of arguments
-        while(min_num > collected && !args.empty()) {
-            std::string current_ = args.back();
-            args.pop_back();
-            op->add_result(current_, result_count);
-            parse_order_.push_back(op.get());
-            collected += result_count;
-        }
-
-        if(min_num > collected) {  // if we have run out of arguments and the minimum was not met
-            throw ArgumentMismatch::TypedAtLeast(op->get_name(), min_num, op->get_type_name());
-        }
-
-        if(max_num > collected || op->get_allow_extra_args()) {  // we allow optional arguments
-            auto remreqpos = _count_remaining_positionals(true);
-            // we have met the minimum now optionally check up to the maximum
-            while((collected < max_num || op->get_allow_extra_args()) && !args.empty() &&
-                  _recognize(args.back(), false) == detail::Classifier::NONE) {
-                // If any required positionals remain, don't keep eating
-                if(remreqpos >= args.size()) {
-                    break;
-                }
-
-                op->add_result(args.back(), result_count);
-                parse_order_.push_back(op.get());
-                args.pop_back();
-                collected += result_count;
-            }
-
-            // Allow -- to end an unlimited list and "eat" it
-            if(!args.empty() && _recognize(args.back()) == detail::Classifier::POSITIONAL_MARK)
-                args.pop_back();
-            // optional flag that didn't receive anything now get the default value
-            if(min_num == 0 && max_num > 0 && collected == 0) {
-                auto res = op->get_flag_value(arg_name, std::string{});
-                op->add_result(res);
-                parse_order_.push_back(op.get());
-            }
-        }
-
-        // if we only partially completed a type then add an empty string for later processing
-        if(min_num > 0 && op->get_type_size_max() != min_num && (collected % op->get_type_size_max()) != 0) {
-            op->add_result(std::string{});
-        }
-        if(op->get_trigger_on_parse()) {
-            op->run_callback();
-        }
-        if(!rest.empty()) {
-            rest = "-" + rest;
-            args.push_back(rest);
-        }
-        return true;
-    }
+    bool _parse_arg(std::vector<std::string> &args, detail::Classifier current_type);
 
     /// Trigger the pre_parse callback if needed
-    void _trigger_pre_parse(std::size_t remaining_args) {
-        if(!pre_parse_called_) {
-            pre_parse_called_ = true;
-            if(pre_parse_callback_) {
-                pre_parse_callback_(remaining_args);
-            }
-        } else if(immediate_callback_) {
-            if(!name_.empty()) {
-                auto pcnt = parsed_;
-                auto extras = std::move(missing_);
-                clear();
-                parsed_ = pcnt;
-                pre_parse_called_ = true;
-                missing_ = std::move(extras);
-            }
-        }
-    }
+    void _trigger_pre_parse(std::size_t remaining_args);
 
     /// Get the appropriate parent to fallthrough to which is the first one that has a name or the main app
-    App *_get_fallthrough_parent() {
-        if(parent_ == nullptr) {
-            throw(HorribleError("No Valid parent"));
-        }
-        auto fallthrough_parent = parent_;
-        while((fallthrough_parent->parent_ != nullptr) && (fallthrough_parent->get_name().empty())) {
-            fallthrough_parent = fallthrough_parent->parent_;
-        }
-        return fallthrough_parent;
-    }
+    App *_get_fallthrough_parent();
 
     /// Helper function to run through all possible comparisons of subcommand names to check there is no overlap
-    const std::string &_compare_subcommand_names(const App &subcom, const App &base) const {
-        static const std::string estring;
-        if(subcom.disabled_) {
-            return estring;
-        }
-        for(auto &subc : base.subcommands_) {
-            if(subc.get() != &subcom) {
-                if(subc->disabled_) {
-                    continue;
-                }
-                if(!subcom.get_name().empty()) {
-                    if(subc->check_name(subcom.get_name())) {
-                        return subcom.get_name();
-                    }
-                }
-                if(!subc->get_name().empty()) {
-                    if(subcom.check_name(subc->get_name())) {
-                        return subc->get_name();
-                    }
-                }
-                for(const auto &les : subcom.aliases_) {
-                    if(subc->check_name(les)) {
-                        return les;
-                    }
-                }
-                // this loop is needed in case of ignore_underscore or ignore_case on one but not the other
-                for(const auto &les : subc->aliases_) {
-                    if(subcom.check_name(les)) {
-                        return les;
-                    }
-                }
-                // if the subcommand is an option group we need to check deeper
-                if(subc->get_name().empty()) {
-                    auto &cmpres = _compare_subcommand_names(subcom, *subc);
-                    if(!cmpres.empty()) {
-                        return cmpres;
-                    }
-                }
-                // if the test subcommand is an option group we need to check deeper
-                if(subcom.get_name().empty()) {
-                    auto &cmpres = _compare_subcommand_names(*subc, subcom);
-                    if(!cmpres.empty()) {
-                        return cmpres;
-                    }
-                }
-            }
-        }
-        return estring;
-    }
+    CLI11_NODISCARD const std::string &_compare_subcommand_names(const App &subcom, const App &base) const;
+
     /// Helper function to place extra values in the most appropriate position
-    void _move_to_missing(detail::Classifier val_type, const std::string &val) {
-        if(allow_extras_ || subcommands_.empty()) {
-            missing_.emplace_back(val_type, val);
-            return;
-        }
-        // allow extra arguments to be places in an option group if it is allowed there
-        for(auto &subc : subcommands_) {
-            if(subc->name_.empty() && subc->allow_extras_) {
-                subc->missing_.emplace_back(val_type, val);
-                return;
-            }
-        }
-        // if we haven't found any place to put them yet put them in missing
-        missing_.emplace_back(val_type, val);
-    }
+    void _move_to_missing(detail::Classifier val_type, const std::string &val);
 
   public:
     /// function that could be used by subclasses of App to shift options around into subcommands
-    void _move_option(Option *opt, App *app) {
-        if(opt == nullptr) {
-            throw OptionNotFound("the option is NULL");
-        }
-        // verify that the give app is actually a subcommand
-        bool found = false;
-        for(auto &subc : subcommands_) {
-            if(app == subc.get()) {
-                found = true;
-            }
-        }
-        if(!found) {
-            throw OptionNotFound("The Given app is not a subcommand");
-        }
-
-        if((help_ptr_ == opt) || (help_all_ptr_ == opt))
-            throw OptionAlreadyAdded("cannot move help options");
-
-        if(config_ptr_ == opt)
-            throw OptionAlreadyAdded("cannot move config file options");
-
-        auto iterator =
-            std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; });
-        if(iterator != std::end(options_)) {
-            const auto &opt_p = *iterator;
-            if(std::find_if(std::begin(app->options_), std::end(app->options_), [&opt_p](const Option_p &v) {
-                   return (*v == *opt_p);
-               }) == std::end(app->options_)) {
-                // only erase after the insertion was successful
-                app->options_.push_back(std::move(*iterator));
-                options_.erase(iterator);
-            } else {
-                throw OptionAlreadyAdded("option was not located: " + opt->get_name());
-            }
-        } else {
-            throw OptionNotFound("could not locate the given Option");
-        }
-    }
+    void _move_option(Option *opt, App *app);
 };  // namespace CLI
 
 /// Extension of App to better manage groups of options
@@ -3012,156 +1317,45 @@ class Option_group : public App {
         return subcom;
     }
 };
-/// Helper function to enable one option group/subcommand when another is used
-inline void TriggerOn(App *trigger_app, App *app_to_enable) {
-    app_to_enable->enabled_by_default(false);
-    app_to_enable->disabled_by_default();
-    trigger_app->preparse_callback([app_to_enable](std::size_t) { app_to_enable->disabled(false); });
-}
 
 /// Helper function to enable one option group/subcommand when another is used
-inline void TriggerOn(App *trigger_app, std::vector<App *> apps_to_enable) {
-    for(auto &app : apps_to_enable) {
-        app->enabled_by_default(false);
-        app->disabled_by_default();
-    }
+CLI11_INLINE void TriggerOn(App *trigger_app, App *app_to_enable);
 
-    trigger_app->preparse_callback([apps_to_enable](std::size_t) {
-        for(auto &app : apps_to_enable) {
-            app->disabled(false);
-        }
-    });
-}
+/// Helper function to enable one option group/subcommand when another is used
+CLI11_INLINE void TriggerOn(App *trigger_app, std::vector<App *> apps_to_enable);
 
 /// Helper function to disable one option group/subcommand when another is used
-inline void TriggerOff(App *trigger_app, App *app_to_enable) {
-    app_to_enable->disabled_by_default(false);
-    app_to_enable->enabled_by_default();
-    trigger_app->preparse_callback([app_to_enable](std::size_t) { app_to_enable->disabled(); });
-}
+CLI11_INLINE void TriggerOff(App *trigger_app, App *app_to_enable);
 
 /// Helper function to disable one option group/subcommand when another is used
-inline void TriggerOff(App *trigger_app, std::vector<App *> apps_to_enable) {
-    for(auto &app : apps_to_enable) {
-        app->disabled_by_default(false);
-        app->enabled_by_default();
-    }
-
-    trigger_app->preparse_callback([apps_to_enable](std::size_t) {
-        for(auto &app : apps_to_enable) {
-            app->disabled();
-        }
-    });
-}
+CLI11_INLINE void TriggerOff(App *trigger_app, std::vector<App *> apps_to_enable);
 
 /// Helper function to mark an option as deprecated
-inline void deprecate_option(Option *opt, const std::string &replacement = "") {
-    Validator deprecate_warning{[opt, replacement](std::string &) {
-                                    std::cout << opt->get_name() << " is deprecated please use '" << replacement
-                                              << "' instead\n";
-                                    return std::string();
-                                },
-                                "DEPRECATED"};
-    deprecate_warning.application_index(0);
-    opt->check(deprecate_warning);
-    if(!replacement.empty()) {
-        opt->description(opt->get_description() + " DEPRECATED: please use '" + replacement + "' instead");
-    }
-}
+CLI11_INLINE void deprecate_option(Option *opt, const std::string &replacement = "");
 
 /// Helper function to mark an option as deprecated
 inline void deprecate_option(App *app, const std::string &option_name, const std::string &replacement = "") {
-    auto opt = app->get_option(option_name);
+    auto *opt = app->get_option(option_name);
     deprecate_option(opt, replacement);
 }
 
 /// Helper function to mark an option as deprecated
 inline void deprecate_option(App &app, const std::string &option_name, const std::string &replacement = "") {
-    auto opt = app.get_option(option_name);
+    auto *opt = app.get_option(option_name);
     deprecate_option(opt, replacement);
 }
 
 /// Helper function to mark an option as retired
-inline void retire_option(App *app, Option *opt) {
-    App temp;
-    auto option_copy = temp.add_option(opt->get_name(false, true))
-                           ->type_size(opt->get_type_size_min(), opt->get_type_size_max())
-                           ->expected(opt->get_expected_min(), opt->get_expected_max())
-                           ->allow_extra_args(opt->get_allow_extra_args());
-
-    app->remove_option(opt);
-    auto opt2 = app->add_option(option_copy->get_name(false, true), "option has been retired and has no effect")
-                    ->type_name("RETIRED")
-                    ->default_str("RETIRED")
-                    ->type_size(option_copy->get_type_size_min(), option_copy->get_type_size_max())
-                    ->expected(option_copy->get_expected_min(), option_copy->get_expected_max())
-                    ->allow_extra_args(option_copy->get_allow_extra_args());
-
-    Validator retired_warning{[opt2](std::string &) {
-                                  std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n";
-                                  return std::string();
-                              },
-                              ""};
-    retired_warning.application_index(0);
-    opt2->check(retired_warning);
-}
+CLI11_INLINE void retire_option(App *app, Option *opt);
 
 /// Helper function to mark an option as retired
-inline void retire_option(App &app, Option *opt) { retire_option(&app, opt); }
+CLI11_INLINE void retire_option(App &app, Option *opt);
 
 /// Helper function to mark an option as retired
-inline void retire_option(App *app, const std::string &option_name) {
-
-    auto opt = app->get_option_no_throw(option_name);
-    if(opt != nullptr) {
-        retire_option(app, opt);
-        return;
-    }
-    auto opt2 = app->add_option(option_name, "option has been retired and has no effect")
-                    ->type_name("RETIRED")
-                    ->expected(0, 1)
-                    ->default_str("RETIRED");
-    Validator retired_warning{[opt2](std::string &) {
-                                  std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n";
-                                  return std::string();
-                              },
-                              ""};
-    retired_warning.application_index(0);
-    opt2->check(retired_warning);
-}
+CLI11_INLINE void retire_option(App *app, const std::string &option_name);
 
 /// Helper function to mark an option as retired
-inline void retire_option(App &app, const std::string &option_name) { retire_option(&app, option_name); }
-
-namespace FailureMessage {
-
-/// Printout a clean, simple message on error (the default in CLI11 1.5+)
-inline std::string simple(const App *app, const Error &e) {
-    std::string header = std::string(e.what()) + "\n";
-    std::vector<std::string> names;
-
-    // Collect names
-    if(app->get_help_ptr() != nullptr)
-        names.push_back(app->get_help_ptr()->get_name());
-
-    if(app->get_help_all_ptr() != nullptr)
-        names.push_back(app->get_help_all_ptr()->get_name());
-
-    // If any names found, suggest those
-    if(!names.empty())
-        header += "Run with " + detail::join(names, " or ") + " for more information.\n";
-
-    return header;
-}
-
-/// Printout the full help string on error (if this fn is set, the old default for CLI11)
-inline std::string help(const App *app, const Error &e) {
-    std::string header = std::string("ERROR: ") + e.get_name() + ": " + e.what() + "\n";
-    header += app->help();
-    return header;
-}
-
-}  // namespace FailureMessage
+CLI11_INLINE void retire_option(App &app, const std::string &option_name);
 
 namespace detail {
 /// This class is simply to allow tests access to App's protected functions
@@ -3199,3 +1393,7 @@ struct AppFriend {
 
 // [CLI11:app_hpp:end]
 }  // namespace CLI
+
+#ifndef CLI11_COMPILE
+#include "impl/App_inl.hpp"
+#endif
diff --git a/include/CLI/CLI.hpp b/include/CLI/CLI.hpp
index 990ba40..ae4fc60 100644
--- a/include/CLI/CLI.hpp
+++ b/include/CLI/CLI.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/include/CLI/ConfigFwd.hpp b/include/CLI/ConfigFwd.hpp
index ef2ac34..a9ae217 100644
--- a/include/CLI/ConfigFwd.hpp
+++ b/include/CLI/ConfigFwd.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -34,7 +34,7 @@ struct ConfigItem {
     std::vector<std::string> inputs{};
 
     /// The list of parents and name joined by "."
-    std::string fullname() const {
+    CLI11_NODISCARD std::string fullname() const {
         std::vector<std::string> tmp = parents;
         tmp.emplace_back(name);
         return detail::join(tmp, ".");
@@ -54,15 +54,18 @@ class Config {
     virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;
 
     /// Get a flag value
-    virtual std::string to_flag(const ConfigItem &item) const {
+    CLI11_NODISCARD virtual std::string to_flag(const ConfigItem &item) const {
         if(item.inputs.size() == 1) {
             return item.inputs.at(0);
         }
-        throw ConversionError::TooManyInputsFlag(item.fullname());
+        if(item.inputs.empty()) {
+            return "{}";
+        }
+        throw ConversionError::TooManyInputsFlag(item.fullname());  // LCOV_EXCL_LINE
     }
 
     /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure
-    std::vector<ConfigItem> from_file(const std::string &name) {
+    CLI11_NODISCARD std::vector<ConfigItem> from_file(const std::string &name) const {
         std::ifstream input{name};
         if(!input.good())
             throw FileError::Missing(name);
@@ -145,7 +148,7 @@ class ConfigBase : public Config {
     /// get a reference to the configuration section
     std::string &sectionRef() { return configSection; }
     /// get the section
-    const std::string &section() const { return configSection; }
+    CLI11_NODISCARD const std::string &section() const { return configSection; }
     /// specify a particular section of the configuration file to use
     ConfigBase *section(const std::string &sectionName) {
         configSection = sectionName;
@@ -155,7 +158,7 @@ class ConfigBase : public Config {
     /// get a reference to the configuration index
     int16_t &indexRef() { return configIndex; }
     /// get the section index
-    int16_t index() const { return configIndex; }
+    CLI11_NODISCARD int16_t index() const { return configIndex; }
     /// specify a particular index in the section to use (-1) for all sections to use
     ConfigBase *index(int16_t sectionIndex) {
         configIndex = sectionIndex;
diff --git a/include/CLI/Error.hpp b/include/CLI/Error.hpp
index de3122b..0900da5 100644
--- a/include/CLI/Error.hpp
+++ b/include/CLI/Error.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -15,6 +15,7 @@
 // [CLI11:public_includes:end]
 
 // CLI library includes
+#include "Macros.hpp"
 #include "StringTools.hpp"
 
 namespace CLI {
@@ -72,9 +73,9 @@ class Error : public std::runtime_error {
     std::string error_name{"Error"};
 
   public:
-    int get_exit_code() const { return actual_exit_code; }
+    CLI11_NODISCARD int get_exit_code() const { return actual_exit_code; }
 
-    std::string get_name() const { return error_name; }
+    CLI11_NODISCARD std::string get_name() const { return error_name; }
 
     Error(std::string name, std::string msg, int exit_code = static_cast<int>(ExitCodes::BaseClass))
         : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {}
@@ -137,10 +138,10 @@ class OptionAlreadyAdded : public ConstructionError {
     explicit OptionAlreadyAdded(std::string name)
         : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {}
     static OptionAlreadyAdded Requires(std::string name, std::string other) {
-        return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded);
+        return {name + " requires " + other, ExitCodes::OptionAlreadyAdded};
     }
     static OptionAlreadyAdded Excludes(std::string name, std::string other) {
-        return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded);
+        return {name + " excludes " + other, ExitCodes::OptionAlreadyAdded};
     }
 };
 
@@ -223,32 +224,30 @@ class RequiredError : public ParseError {
         if(min_subcom == 1) {
             return RequiredError("A subcommand");
         }
-        return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands",
-                             ExitCodes::RequiredError);
+        return {"Requires at least " + std::to_string(min_subcom) + " subcommands", ExitCodes::RequiredError};
     }
     static RequiredError
     Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) {
         if((min_option == 1) && (max_option == 1) && (used == 0))
             return RequiredError("Exactly 1 option from [" + option_list + "]");
         if((min_option == 1) && (max_option == 1) && (used > 1)) {
-            return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) +
-                                     " were given",
-                                 ExitCodes::RequiredError);
+            return {"Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) +
+                        " were given",
+                    ExitCodes::RequiredError};
         }
         if((min_option == 1) && (used == 0))
             return RequiredError("At least 1 option from [" + option_list + "]");
         if(used < min_option) {
-            return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " +
-                                     std::to_string(used) + "were given from [" + option_list + "]",
-                                 ExitCodes::RequiredError);
+            return {"Requires at least " + std::to_string(min_option) + " options used and only " +
+                        std::to_string(used) + "were given from [" + option_list + "]",
+                    ExitCodes::RequiredError};
         }
         if(max_option == 1)
-            return RequiredError("Requires at most 1 options be given from [" + option_list + "]",
-                                 ExitCodes::RequiredError);
+            return {"Requires at most 1 options be given from [" + option_list + "]", ExitCodes::RequiredError};
 
-        return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " +
-                                 std::to_string(used) + "were given from [" + option_list + "]",
-                             ExitCodes::RequiredError);
+        return {"Requires at most " + std::to_string(max_option) + " options be used and " + std::to_string(used) +
+                    "were given from [" + option_list + "]",
+                ExitCodes::RequiredError};
     }
 };
 
@@ -277,6 +276,10 @@ class ArgumentMismatch : public ParseError {
     static ArgumentMismatch FlagOverride(std::string name) {
         return ArgumentMismatch(name + " was given a disallowed flag override");
     }
+    static ArgumentMismatch PartialType(std::string name, int num, std::string type) {
+        return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) +
+                                " required for each element");
+    }
 };
 
 /// Thrown when a requires option is missing
diff --git a/include/CLI/FormatterFwd.hpp b/include/CLI/FormatterFwd.hpp
index 728926c..5ef0a5b 100644
--- a/include/CLI/FormatterFwd.hpp
+++ b/include/CLI/FormatterFwd.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -7,6 +7,7 @@
 #pragma once
 
 // [CLI11:public_includes:set]
+#include <functional>
 #include <map>
 #include <string>
 #include <utility>
@@ -56,6 +57,8 @@ class FormatterBase {
     FormatterBase() = default;
     FormatterBase(const FormatterBase &) = default;
     FormatterBase(FormatterBase &&) = default;
+    FormatterBase &operator=(const FormatterBase &) = default;
+    FormatterBase &operator=(FormatterBase &&) = default;
 
     /// Adding a destructor in this form to work around bug in GCC 4.7
     virtual ~FormatterBase() noexcept {}  // NOLINT(modernize-use-equals-default)
@@ -78,15 +81,14 @@ class FormatterBase {
     ///@{
 
     /// Get the current value of a name (REQUIRED, etc.)
-    std::string get_label(std::string key) const {
+    CLI11_NODISCARD std::string get_label(std::string key) const {
         if(labels_.find(key) == labels_.end())
             return key;
-        else
-            return labels_.at(key);
+        return labels_.at(key);
     }
 
     /// Get the current column width
-    std::size_t get_column_width() const { return column_width_; }
+    CLI11_NODISCARD std::size_t get_column_width() const { return column_width_; }
 
     ///@}
 };
@@ -118,13 +120,16 @@ class Formatter : public FormatterBase {
     Formatter() = default;
     Formatter(const Formatter &) = default;
     Formatter(Formatter &&) = default;
+    Formatter &operator=(const Formatter &) = default;
+    Formatter &operator=(Formatter &&) = default;
 
     /// @name Overridables
     ///@{
 
     /// This prints out a group of options with title
     ///
-    virtual std::string make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const;
+    CLI11_NODISCARD virtual std::string
+    make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const;
 
     /// This prints out just the positionals "group"
     virtual std::string make_positionals(const App *app) const;
diff --git a/include/CLI/Macros.hpp b/include/CLI/Macros.hpp
index f228c91..e854390 100644
--- a/include/CLI/Macros.hpp
+++ b/include/CLI/Macros.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -26,7 +26,7 @@
 #define CLI11_CPP14
 #if _MSVC_LANG > 201402L && _MSC_VER >= 1910
 #define CLI11_CPP17
-#if __MSVC_LANG > 201703L && _MSC_VER >= 1910
+#if _MSVC_LANG > 201703L && _MSC_VER >= 1910
 #define CLI11_CPP20
 #endif
 #endif
@@ -41,4 +41,35 @@
 #define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason)))
 #endif
 
+// GCC < 10 doesn't ignore this in unevaluated contexts
+#if !defined(CLI11_CPP17) ||                                                                                           \
+    (defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 10 && __GNUC__ > 4)
+#define CLI11_NODISCARD
+#else
+#define CLI11_NODISCARD [[nodiscard]]
+#endif
+
+/** detection of rtti */
+#ifndef CLI11_USE_STATIC_RTTI
+#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI)
+#define CLI11_USE_STATIC_RTTI 1
+#elif defined(__cpp_rtti)
+#if(defined(_CPPRTTI) && _CPPRTTI == 0)
+#define CLI11_USE_STATIC_RTTI 1
+#else
+#define CLI11_USE_STATIC_RTTI 0
+#endif
+#elif(defined(__GCC_RTTI) && __GXX_RTTI)
+#define CLI11_USE_STATIC_RTTI 0
+#else
+#define CLI11_USE_STATIC_RTTI 1
+#endif
+#endif
+
+/** Inline macro **/
+#ifdef CLI11_COMPILE
+#define CLI11_INLINE
+#else
+#define CLI11_INLINE inline
+#endif
 // [CLI11:macros_hpp:end]
diff --git a/include/CLI/Option.hpp b/include/CLI/Option.hpp
index 25a6760..d323507 100644
--- a/include/CLI/Option.hpp
+++ b/include/CLI/Option.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -40,7 +40,8 @@ enum class MultiOptionPolicy : char {
     TakeLast,   //!< take only the last Expected number of arguments
     TakeFirst,  //!< take only the first Expected number of arguments
     Join,       //!< merge all the arguments together into a single string via the delimiter character default('\n')
-    TakeAll     //!< just get all the passed argument regardless
+    TakeAll,    //!< just get all the passed argument regardless
+    Sum         //!< sum all the arguments together if numerical or concatenate directly without delimiter
 };
 
 /// This is the CRTP base class for Option and OptionDefaults. It was designed this way
@@ -77,17 +78,7 @@ template <typename CRTP> class OptionBase {
     MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
 
     /// Copy the contents to another similar class (one based on OptionBase)
-    template <typename T> void copy_to(T *other) const {
-        other->group(group_);
-        other->required(required_);
-        other->ignore_case(ignore_case_);
-        other->ignore_underscore(ignore_underscore_);
-        other->configurable(configurable_);
-        other->disable_flag_override(disable_flag_override_);
-        other->delimiter(delimiter_);
-        other->always_capture_default(always_capture_default_);
-        other->multi_option_policy(multi_option_policy_);
-    }
+    template <typename T> void copy_to(T *other) const;
 
   public:
     // setters
@@ -118,44 +109,44 @@ template <typename CRTP> class OptionBase {
     // Getters
 
     /// Get the group of this option
-    const std::string &get_group() const { return group_; }
+    CLI11_NODISCARD const std::string &get_group() const { return group_; }
 
     /// True if this is a required option
-    bool get_required() const { return required_; }
+    CLI11_NODISCARD bool get_required() const { return required_; }
 
     /// The status of ignore case
-    bool get_ignore_case() const { return ignore_case_; }
+    CLI11_NODISCARD bool get_ignore_case() const { return ignore_case_; }
 
     /// The status of ignore_underscore
-    bool get_ignore_underscore() const { return ignore_underscore_; }
+    CLI11_NODISCARD bool get_ignore_underscore() const { return ignore_underscore_; }
 
     /// The status of configurable
-    bool get_configurable() const { return configurable_; }
+    CLI11_NODISCARD bool get_configurable() const { return configurable_; }
 
     /// The status of configurable
-    bool get_disable_flag_override() const { return disable_flag_override_; }
+    CLI11_NODISCARD bool get_disable_flag_override() const { return disable_flag_override_; }
 
     /// Get the current delimiter char
-    char get_delimiter() const { return delimiter_; }
+    CLI11_NODISCARD char get_delimiter() const { return delimiter_; }
 
     /// Return true if this will automatically capture the default value for help printing
-    bool get_always_capture_default() const { return always_capture_default_; }
+    CLI11_NODISCARD bool get_always_capture_default() const { return always_capture_default_; }
 
     /// The status of the multi option policy
-    MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }
+    CLI11_NODISCARD MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }
 
     // Shortcuts for multi option policy
 
     /// Set the multi option policy to take last
     CRTP *take_last() {
-        auto self = static_cast<CRTP *>(this);
+        auto *self = static_cast<CRTP *>(this);
         self->multi_option_policy(MultiOptionPolicy::TakeLast);
         return self;
     }
 
     /// Set the multi option policy to take last
     CRTP *take_first() {
-        auto self = static_cast<CRTP *>(this);
+        auto *self = static_cast<CRTP *>(this);
         self->multi_option_policy(MultiOptionPolicy::TakeFirst);
         return self;
     }
@@ -169,7 +160,7 @@ template <typename CRTP> class OptionBase {
 
     /// Set the multi option policy to join
     CRTP *join() {
-        auto self = static_cast<CRTP *>(this);
+        auto *self = static_cast<CRTP *>(this);
         self->multi_option_policy(MultiOptionPolicy::Join);
         return self;
     }
@@ -360,10 +351,10 @@ class Option : public OptionBase<Option> {
     Option &operator=(const Option &) = delete;
 
     /// Count the total number of times an option was passed
-    std::size_t count() const { return results_.size(); }
+    CLI11_NODISCARD std::size_t count() const { return results_.size(); }
 
     /// True if the option was not passed
-    bool empty() const { return results_.empty(); }
+    CLI11_NODISCARD bool empty() const { return results_.empty(); }
 
     /// This bool operator returns true if any arguments were passed or the option callback is forced
     explicit operator bool() const { return !empty() || force_callback_; }
@@ -379,46 +370,11 @@ class Option : public OptionBase<Option> {
     ///@{
 
     /// Set the number of expected arguments
-    Option *expected(int value) {
-        if(value < 0) {
-            expected_min_ = -value;
-            if(expected_max_ < expected_min_) {
-                expected_max_ = expected_min_;
-            }
-            allow_extra_args_ = true;
-            flag_like_ = false;
-        } else if(value == detail::expected_max_vector_size) {
-            expected_min_ = 1;
-            expected_max_ = detail::expected_max_vector_size;
-            allow_extra_args_ = true;
-            flag_like_ = false;
-        } else {
-            expected_min_ = value;
-            expected_max_ = value;
-            flag_like_ = (expected_min_ == 0);
-        }
-        return this;
-    }
+    Option *expected(int value);
 
     /// Set the range of expected arguments
-    Option *expected(int value_min, int value_max) {
-        if(value_min < 0) {
-            value_min = -value_min;
-        }
-
-        if(value_max < 0) {
-            value_max = detail::expected_max_vector_size;
-        }
-        if(value_max < value_min) {
-            expected_min_ = value_max;
-            expected_max_ = value_min;
-        } else {
-            expected_max_ = value_max;
-            expected_min_ = value_min;
-        }
+    Option *expected(int value_min, int value_max);
 
-        return this;
-    }
     /// Set the value of allow_extra_args which allows extra value arguments on the flag or option to be included
     /// with each instance
     Option *allow_extra_args(bool value = true) {
@@ -426,14 +382,14 @@ class Option : public OptionBase<Option> {
         return this;
     }
     /// Get the current value of allow extra args
-    bool get_allow_extra_args() const { return allow_extra_args_; }
+    CLI11_NODISCARD bool get_allow_extra_args() const { return allow_extra_args_; }
     /// Set the value of trigger_on_parse which specifies that the option callback should be triggered on every parse
     Option *trigger_on_parse(bool value = true) {
         trigger_on_result_ = value;
         return this;
     }
     /// The status of trigger on parse
-    bool get_trigger_on_parse() const { return trigger_on_result_; }
+    CLI11_NODISCARD bool get_trigger_on_parse() const { return trigger_on_result_; }
 
     /// Set the value of force_callback
     Option *force_callback(bool value = true) {
@@ -441,7 +397,7 @@ class Option : public OptionBase<Option> {
         return this;
     }
     /// The status of force_callback
-    bool get_force_callback() const { return force_callback_; }
+    CLI11_NODISCARD bool get_force_callback() const { return force_callback_; }
 
     /// Set the value of run_callback_for_default which controls whether the callback function should be called to set
     /// the default This is controlled automatically but could be manipulated by the user.
@@ -450,81 +406,32 @@ class Option : public OptionBase<Option> {
         return this;
     }
     /// Get the current value of run_callback_for_default
-    bool get_run_callback_for_default() const { return run_callback_for_default_; }
+    CLI11_NODISCARD bool get_run_callback_for_default() const { return run_callback_for_default_; }
 
     /// Adds a Validator with a built in type name
-    Option *check(Validator validator, const std::string &validator_name = "") {
-        validator.non_modifying();
-        validators_.push_back(std::move(validator));
-        if(!validator_name.empty())
-            validators_.back().name(validator_name);
-        return this;
-    }
+    Option *check(Validator validator, const std::string &validator_name = "");
 
     /// Adds a Validator. Takes a const string& and returns an error message (empty if conversion/check is okay).
     Option *check(std::function<std::string(const std::string &)> Validator,
                   std::string Validator_description = "",
-                  std::string Validator_name = "") {
-        validators_.emplace_back(Validator, std::move(Validator_description), std::move(Validator_name));
-        validators_.back().non_modifying();
-        return this;
-    }
+                  std::string Validator_name = "");
 
     /// Adds a transforming Validator with a built in type name
-    Option *transform(Validator Validator, const std::string &Validator_name = "") {
-        validators_.insert(validators_.begin(), std::move(Validator));
-        if(!Validator_name.empty())
-            validators_.front().name(Validator_name);
-        return this;
-    }
+    Option *transform(Validator Validator, const std::string &Validator_name = "");
 
     /// Adds a Validator-like function that can change result
     Option *transform(const std::function<std::string(std::string)> &func,
                       std::string transform_description = "",
-                      std::string transform_name = "") {
-        validators_.insert(validators_.begin(),
-                           Validator(
-                               [func](std::string &val) {
-                                   val = func(val);
-                                   return std::string{};
-                               },
-                               std::move(transform_description),
-                               std::move(transform_name)));
-
-        return this;
-    }
+                      std::string transform_name = "");
 
     /// Adds a user supplied function to run on each item passed in (communicate though lambda capture)
-    Option *each(const std::function<void(std::string)> &func) {
-        validators_.emplace_back(
-            [func](std::string &inout) {
-                func(inout);
-                return std::string{};
-            },
-            std::string{});
-        return this;
-    }
+    Option *each(const std::function<void(std::string)> &func);
+
     /// Get a named Validator
-    Validator *get_validator(const std::string &Validator_name = "") {
-        for(auto &Validator : validators_) {
-            if(Validator_name == Validator.get_name()) {
-                return &Validator;
-            }
-        }
-        if((Validator_name.empty()) && (!validators_.empty())) {
-            return &(validators_.front());
-        }
-        throw OptionNotFound(std::string{"Validator "} + Validator_name + " Not Found");
-    }
+    Validator *get_validator(const std::string &Validator_name = "");
 
     /// Get a Validator by index NOTE: this may not be the order of definition
-    Validator *get_validator(int index) {
-        // This is an signed int so that it is not equivalent to a pointer.
-        if(index >= 0 && index < static_cast<int>(validators_.size())) {
-            return &(validators_[static_cast<decltype(validators_)::size_type>(index)]);
-        }
-        throw OptionNotFound("Validator index is not valid");
-    }
+    Validator *get_validator(int index);
 
     /// Sets required options
     Option *needs(Option *opt) {
@@ -546,35 +453,14 @@ class Option : public OptionBase<Option> {
     /// Any number supported, any mix of string and Opt
     template <typename A, typename B, typename... ARG> Option *needs(A opt, B opt1, ARG... args) {
         needs(opt);
-        return needs(opt1, args...);
+        return needs(opt1, args...);  // NOLINT(readability-suspicious-call-argument)
     }
 
     /// Remove needs link from an option. Returns true if the option really was in the needs list.
-    bool remove_needs(Option *opt) {
-        auto iterator = std::find(std::begin(needs_), std::end(needs_), opt);
-
-        if(iterator == std::end(needs_)) {
-            return false;
-        }
-        needs_.erase(iterator);
-        return true;
-    }
+    bool remove_needs(Option *opt);
 
     /// Sets excluded options
-    Option *excludes(Option *opt) {
-        if(opt == this) {
-            throw(IncorrectConstruction("and option cannot exclude itself"));
-        }
-        excludes_.insert(opt);
-
-        // Help text should be symmetric - excluding a should exclude b
-        opt->excludes_.insert(this);
-
-        // Ignoring the insert return value, excluding twice is now allowed.
-        // (Mostly to allow both directions to be excluded by user, even though the library does it for you.)
-
-        return this;
-    }
+    Option *excludes(Option *opt);
 
     /// Can find a string if needed
     template <typename T = App> Option *excludes(std::string opt_name) {
@@ -592,15 +478,7 @@ class Option : public OptionBase<Option> {
     }
 
     /// Remove needs link from an option. Returns true if the option really was in the needs list.
-    bool remove_excludes(Option *opt) {
-        auto iterator = std::find(std::begin(excludes_), std::end(excludes_), opt);
-
-        if(iterator == std::end(excludes_)) {
-            return false;
-        }
-        excludes_.erase(iterator);
-        return true;
-    }
+    bool remove_excludes(Option *opt);
 
     /// Sets environment variable to read if no option given
     Option *envname(std::string name) {
@@ -612,64 +490,16 @@ class Option : public OptionBase<Option> {
     ///
     /// The template hides the fact that we don't have the definition of App yet.
     /// You are never expected to add an argument to the template here.
-    template <typename T = App> Option *ignore_case(bool value = true) {
-        if(!ignore_case_ && value) {
-            ignore_case_ = value;
-            auto *parent = static_cast<T *>(parent_);
-            for(const Option_p &opt : parent->options_) {
-                if(opt.get() == this) {
-                    continue;
-                }
-                auto &omatch = opt->matching_name(*this);
-                if(!omatch.empty()) {
-                    ignore_case_ = false;
-                    throw OptionAlreadyAdded("adding ignore case caused a name conflict with " + omatch);
-                }
-            }
-        } else {
-            ignore_case_ = value;
-        }
-        return this;
-    }
+    template <typename T = App> Option *ignore_case(bool value = true);
 
     /// Ignore underscores in the option names
     ///
     /// The template hides the fact that we don't have the definition of App yet.
     /// You are never expected to add an argument to the template here.
-    template <typename T = App> Option *ignore_underscore(bool value = true) {
-
-        if(!ignore_underscore_ && value) {
-            ignore_underscore_ = value;
-            auto *parent = static_cast<T *>(parent_);
-            for(const Option_p &opt : parent->options_) {
-                if(opt.get() == this) {
-                    continue;
-                }
-                auto &omatch = opt->matching_name(*this);
-                if(!omatch.empty()) {
-                    ignore_underscore_ = false;
-                    throw OptionAlreadyAdded("adding ignore underscore caused a name conflict with " + omatch);
-                }
-            }
-        } else {
-            ignore_underscore_ = value;
-        }
-        return this;
-    }
+    template <typename T = App> Option *ignore_underscore(bool value = true);
 
     /// Take the last argument if given multiple times (or another policy)
-    Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
-        if(value != multi_option_policy_) {
-            if(multi_option_policy_ == MultiOptionPolicy::Throw && expected_max_ == detail::expected_max_vector_size &&
-               expected_min_ > 1) {  // this bizarre condition is to maintain backwards compatibility
-                                     // with the previous behavior of expected_ with vectors
-                expected_max_ = expected_min_;
-            }
-            multi_option_policy_ = value;
-            current_option_state_ = option_state::parsing;
-        }
-        return this;
-    }
+    Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw);
 
     /// Disable flag overrides values, e.g. --flag=<value> is not allowed
     Option *disable_flag_override(bool value = true) {
@@ -681,41 +511,41 @@ class Option : public OptionBase<Option> {
     ///@{
 
     /// The number of arguments the option expects
-    int get_type_size() const { return type_size_min_; }
+    CLI11_NODISCARD int get_type_size() const { return type_size_min_; }
 
     /// The minimum number of arguments the option expects
-    int get_type_size_min() const { return type_size_min_; }
+    CLI11_NODISCARD int get_type_size_min() const { return type_size_min_; }
     /// The maximum number of arguments the option expects
-    int get_type_size_max() const { return type_size_max_; }
+    CLI11_NODISCARD int get_type_size_max() const { return type_size_max_; }
 
     /// Return the inject_separator flag
-    int get_inject_separator() const { return inject_separator_; }
+    CLI11_NODISCARD bool get_inject_separator() const { return inject_separator_; }
 
     /// The environment variable associated to this value
-    std::string get_envname() const { return envname_; }
+    CLI11_NODISCARD std::string get_envname() const { return envname_; }
 
     /// The set of options needed
-    std::set<Option *> get_needs() const { return needs_; }
+    CLI11_NODISCARD std::set<Option *> get_needs() const { return needs_; }
 
     /// The set of options excluded
-    std::set<Option *> get_excludes() const { return excludes_; }
+    CLI11_NODISCARD std::set<Option *> get_excludes() const { return excludes_; }
 
     /// The default value (for help printing)
-    std::string get_default_str() const { return default_str_; }
+    CLI11_NODISCARD std::string get_default_str() const { return default_str_; }
 
     /// Get the callback function
-    callback_t get_callback() const { return callback_; }
+    CLI11_NODISCARD callback_t get_callback() const { return callback_; }
 
     /// Get the long names
-    const std::vector<std::string> &get_lnames() const { return lnames_; }
+    CLI11_NODISCARD const std::vector<std::string> &get_lnames() const { return lnames_; }
 
     /// Get the short names
-    const std::vector<std::string> &get_snames() const { return snames_; }
+    CLI11_NODISCARD const std::vector<std::string> &get_snames() const { return snames_; }
 
     /// Get the flag names with specified default values
-    const std::vector<std::string> &get_fnames() const { return fnames_; }
+    CLI11_NODISCARD const std::vector<std::string> &get_fnames() const { return fnames_; }
     /// Get a single name for the option, first of lname, pname, sname, envname
-    const std::string &get_single_name() const {
+    CLI11_NODISCARD const std::string &get_single_name() const {
         if(!lnames_.empty()) {
             return lnames_[0];
         }
@@ -728,35 +558,35 @@ class Option : public OptionBase<Option> {
         return envname_;
     }
     /// The number of times the option expects to be included
-    int get_expected() const { return expected_min_; }
+    CLI11_NODISCARD int get_expected() const { return expected_min_; }
 
     /// The number of times the option expects to be included
-    int get_expected_min() const { return expected_min_; }
+    CLI11_NODISCARD int get_expected_min() const { return expected_min_; }
     /// The max number of times the option expects to be included
-    int get_expected_max() const { return expected_max_; }
+    CLI11_NODISCARD int get_expected_max() const { return expected_max_; }
 
     /// The total min number of expected  string values to be used
-    int get_items_expected_min() const { return type_size_min_ * expected_min_; }
+    CLI11_NODISCARD int get_items_expected_min() const { return type_size_min_ * expected_min_; }
 
     /// Get the maximum number of items expected to be returned and used for the callback
-    int get_items_expected_max() const {
+    CLI11_NODISCARD int get_items_expected_max() const {
         int t = type_size_max_;
         return detail::checked_multiply(t, expected_max_) ? t : detail::expected_max_vector_size;
     }
     /// The total min number of expected  string values to be used
-    int get_items_expected() const { return get_items_expected_min(); }
+    CLI11_NODISCARD int get_items_expected() const { return get_items_expected_min(); }
 
     /// True if the argument can be given directly
-    bool get_positional() const { return pname_.length() > 0; }
+    CLI11_NODISCARD bool get_positional() const { return pname_.length() > 0; }
 
     /// True if option has at least one non-positional name
-    bool nonpositional() const { return (snames_.size() + lnames_.size()) > 0; }
+    CLI11_NODISCARD bool nonpositional() const { return (snames_.size() + lnames_.size()) > 0; }
 
     /// True if option has description
-    bool has_description() const { return description_.length() > 0; }
+    CLI11_NODISCARD bool has_description() const { return description_.length() > 0; }
 
     /// Get the description
-    const std::string &get_description() const { return description_; }
+    CLI11_NODISCARD const std::string &get_description() const { return description_; }
 
     /// Set the description
     Option *description(std::string option_description) {
@@ -769,7 +599,7 @@ class Option : public OptionBase<Option> {
         return this;
     }
 
-    const std::string &get_option_text() const { return option_text_; }
+    CLI11_NODISCARD const std::string &get_option_text() const { return option_text_; }
 
     ///@}
     /// @name Help tools
@@ -779,158 +609,38 @@ class Option : public OptionBase<Option> {
     /// Will include / prefer the positional name if positional is true.
     /// If all_options is false, pick just the most descriptive name to show.
     /// Use `get_name(true)` to get the positional name (replaces `get_pname`)
-    std::string get_name(bool positional = false,  ///< Show the positional name
-                         bool all_options = false  ///< Show every option
-    ) const {
-        if(get_group().empty())
-            return {};  // Hidden
-
-        if(all_options) {
-
-            std::vector<std::string> name_list;
-
-            /// The all list will never include a positional unless asked or that's the only name.
-            if((positional && (!pname_.empty())) || (snames_.empty() && lnames_.empty())) {
-                name_list.push_back(pname_);
-            }
-            if((get_items_expected() == 0) && (!fnames_.empty())) {
-                for(const std::string &sname : snames_) {
-                    name_list.push_back("-" + sname);
-                    if(check_fname(sname)) {
-                        name_list.back() += "{" + get_flag_value(sname, "") + "}";
-                    }
-                }
-
-                for(const std::string &lname : lnames_) {
-                    name_list.push_back("--" + lname);
-                    if(check_fname(lname)) {
-                        name_list.back() += "{" + get_flag_value(lname, "") + "}";
-                    }
-                }
-            } else {
-                for(const std::string &sname : snames_)
-                    name_list.push_back("-" + sname);
-
-                for(const std::string &lname : lnames_)
-                    name_list.push_back("--" + lname);
-            }
-
-            return detail::join(name_list);
-        }
-
-        // This returns the positional name no matter what
-        if(positional)
-            return pname_;
-
-        // Prefer long name
-        if(!lnames_.empty())
-            return std::string(2, '-') + lnames_[0];
-
-        // Or short name if no long name
-        if(!snames_.empty())
-            return std::string(1, '-') + snames_[0];
-
-        // If positional is the only name, it's okay to use that
-        return pname_;
-    }
+    CLI11_NODISCARD std::string get_name(bool positional = false,  ///< Show the positional name
+                                         bool all_options = false  ///< Show every option
+    ) const;
 
     ///@}
     /// @name Parser tools
     ///@{
 
     /// Process the callback
-    void run_callback() {
-        if(force_callback_ && results_.empty()) {
-            add_result(default_str_);
-        }
-        if(current_option_state_ == option_state::parsing) {
-            _validate_results(results_);
-            current_option_state_ = option_state::validated;
-        }
-
-        if(current_option_state_ < option_state::reduced) {
-            _reduce_results(proc_results_, results_);
-            current_option_state_ = option_state::reduced;
-        }
-        if(current_option_state_ >= option_state::reduced) {
-            current_option_state_ = option_state::callback_run;
-            if(!(callback_)) {
-                return;
-            }
-            const results_t &send_results = proc_results_.empty() ? results_ : proc_results_;
-            bool local_result = callback_(send_results);
-
-            if(!local_result)
-                throw ConversionError(get_name(), results_);
-        }
-    }
+    void run_callback();
 
     /// If options share any of the same names, find it
-    const std::string &matching_name(const Option &other) const {
-        static const std::string estring;
-        for(const std::string &sname : snames_)
-            if(other.check_sname(sname))
-                return sname;
-        for(const std::string &lname : lnames_)
-            if(other.check_lname(lname))
-                return lname;
-
-        if(ignore_case_ ||
-           ignore_underscore_) {  // We need to do the inverse, in case we are ignore_case or ignore underscore
-            for(const std::string &sname : other.snames_)
-                if(check_sname(sname))
-                    return sname;
-            for(const std::string &lname : other.lnames_)
-                if(check_lname(lname))
-                    return lname;
-        }
-        return estring;
-    }
+    CLI11_NODISCARD const std::string &matching_name(const Option &other) const;
+
     /// If options share any of the same names, they are equal (not counting positional)
     bool operator==(const Option &other) const { return !matching_name(other).empty(); }
 
     /// Check a name. Requires "-" or "--" for short / long, supports positional name
-    bool check_name(const std::string &name) const {
-
-        if(name.length() > 2 && name[0] == '-' && name[1] == '-')
-            return check_lname(name.substr(2));
-        if(name.length() > 1 && name.front() == '-')
-            return check_sname(name.substr(1));
-        if(!pname_.empty()) {
-            std::string local_pname = pname_;
-            std::string local_name = name;
-            if(ignore_underscore_) {
-                local_pname = detail::remove_underscore(local_pname);
-                local_name = detail::remove_underscore(local_name);
-            }
-            if(ignore_case_) {
-                local_pname = detail::to_lower(local_pname);
-                local_name = detail::to_lower(local_name);
-            }
-            if(local_name == local_pname) {
-                return true;
-            }
-        }
-
-        if(!envname_.empty()) {
-            // this needs to be the original since envname_ shouldn't match on case insensitivity
-            return (name == envname_);
-        }
-        return false;
-    }
+    CLI11_NODISCARD bool check_name(const std::string &name) const;
 
     /// Requires "-" to be removed from string
-    bool check_sname(std::string name) const {
+    CLI11_NODISCARD bool check_sname(std::string name) const {
         return (detail::find_member(std::move(name), snames_, ignore_case_) >= 0);
     }
 
     /// Requires "--" to be removed from string
-    bool check_lname(std::string name) const {
+    CLI11_NODISCARD bool check_lname(std::string name) const {
         return (detail::find_member(std::move(name), lnames_, ignore_case_, ignore_underscore_) >= 0);
     }
 
     /// Requires "--" to be removed from string
-    bool check_fname(std::string name) const {
+    CLI11_NODISCARD bool check_fname(std::string name) const {
         if(fnames_.empty()) {
             return false;
         }
@@ -939,97 +649,26 @@ class Option : public OptionBase<Option> {
 
     /// Get the value that goes for a flag, nominally gets the default value but allows for overrides if not
     /// disabled
-    std::string get_flag_value(const std::string &name, std::string input_value) const {
-        static const std::string trueString{"true"};
-        static const std::string falseString{"false"};
-        static const std::string emptyString{"{}"};
-        // check for disable flag override_
-        if(disable_flag_override_) {
-            if(!((input_value.empty()) || (input_value == emptyString))) {
-                auto default_ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
-                if(default_ind >= 0) {
-                    // We can static cast this to std::size_t because it is more than 0 in this block
-                    if(default_flag_values_[static_cast<std::size_t>(default_ind)].second != input_value) {
-                        throw(ArgumentMismatch::FlagOverride(name));
-                    }
-                } else {
-                    if(input_value != trueString) {
-                        throw(ArgumentMismatch::FlagOverride(name));
-                    }
-                }
-            }
-        }
-        auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
-        if((input_value.empty()) || (input_value == emptyString)) {
-            if(flag_like_) {
-                return (ind < 0) ? trueString : default_flag_values_[static_cast<std::size_t>(ind)].second;
-            } else {
-                return (ind < 0) ? default_str_ : default_flag_values_[static_cast<std::size_t>(ind)].second;
-            }
-        }
-        if(ind < 0) {
-            return input_value;
-        }
-        if(default_flag_values_[static_cast<std::size_t>(ind)].second == falseString) {
-            try {
-                auto val = detail::to_flag_value(input_value);
-                return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
-            } catch(const std::invalid_argument &) {
-                return input_value;
-            }
-        } else {
-            return input_value;
-        }
-    }
+    CLI11_NODISCARD std::string get_flag_value(const std::string &name, std::string input_value) const;
 
     /// Puts a result at the end
-    Option *add_result(std::string s) {
-        _add_result(std::move(s), results_);
-        current_option_state_ = option_state::parsing;
-        return this;
-    }
+    Option *add_result(std::string s);
 
     /// Puts a result at the end and get a count of the number of arguments actually added
-    Option *add_result(std::string s, int &results_added) {
-        results_added = _add_result(std::move(s), results_);
-        current_option_state_ = option_state::parsing;
-        return this;
-    }
+    Option *add_result(std::string s, int &results_added);
 
     /// Puts a result at the end
-    Option *add_result(std::vector<std::string> s) {
-        current_option_state_ = option_state::parsing;
-        for(auto &str : s) {
-            _add_result(std::move(str), results_);
-        }
-        return this;
-    }
+    Option *add_result(std::vector<std::string> s);
 
     /// Get the current complete results set
-    const results_t &results() const { return results_; }
+    CLI11_NODISCARD const results_t &results() const { return results_; }
 
     /// Get a copy of the results
-    results_t reduced_results() const {
-        results_t res = proc_results_.empty() ? results_ : proc_results_;
-        if(current_option_state_ < option_state::reduced) {
-            if(current_option_state_ == option_state::parsing) {
-                res = results_;
-                _validate_results(res);
-            }
-            if(!res.empty()) {
-                results_t extra;
-                _reduce_results(extra, res);
-                if(!extra.empty()) {
-                    res = std::move(extra);
-                }
-            }
-        }
-        return res;
-    }
+    CLI11_NODISCARD results_t reduced_results() const;
 
     /// Get the results as a specified type
     template <typename T> void results(T &output) const {
-        bool retval;
+        bool retval = false;
         if(current_option_state_ >= option_state::reduced || (results_.size() == 1 && validators_.empty())) {
             const results_t &res = (proc_results_.empty()) ? results_ : proc_results_;
             retval = detail::lexical_conversion<T, T>(res, output);
@@ -1059,14 +698,14 @@ class Option : public OptionBase<Option> {
     }
 
     /// Return the results as the specified type
-    template <typename T> T as() const {
+    template <typename T> CLI11_NODISCARD T as() const {
         T output;
         results(output);
         return output;
     }
 
     /// See if the callback has been run already
-    bool get_callback_run() const { return (current_option_state_ == option_state::callback_run); }
+    CLI11_NODISCARD bool get_callback_run() const { return (current_option_state_ == option_state::callback_run); }
 
     ///@}
     /// @name Custom options
@@ -1085,48 +724,10 @@ class Option : public OptionBase<Option> {
     }
 
     /// Set a custom option size
-    Option *type_size(int option_type_size) {
-        if(option_type_size < 0) {
-            // this section is included for backwards compatibility
-            type_size_max_ = -option_type_size;
-            type_size_min_ = -option_type_size;
-            expected_max_ = detail::expected_max_vector_size;
-        } else {
-            type_size_max_ = option_type_size;
-            if(type_size_max_ < detail::expected_max_vector_size) {
-                type_size_min_ = option_type_size;
-            } else {
-                inject_separator_ = true;
-            }
-            if(type_size_max_ == 0)
-                required_ = false;
-        }
-        return this;
-    }
-    /// Set a custom option type size range
-    Option *type_size(int option_type_size_min, int option_type_size_max) {
-        if(option_type_size_min < 0 || option_type_size_max < 0) {
-            // this section is included for backwards compatibility
-            expected_max_ = detail::expected_max_vector_size;
-            option_type_size_min = (std::abs)(option_type_size_min);
-            option_type_size_max = (std::abs)(option_type_size_max);
-        }
+    Option *type_size(int option_type_size);
 
-        if(option_type_size_min > option_type_size_max) {
-            type_size_max_ = option_type_size_min;
-            type_size_min_ = option_type_size_max;
-        } else {
-            type_size_min_ = option_type_size_min;
-            type_size_max_ = option_type_size_max;
-        }
-        if(type_size_max_ == 0) {
-            required_ = false;
-        }
-        if(type_size_max_ >= detail::expected_max_vector_size) {
-            inject_separator_ = true;
-        }
-        return this;
-    }
+    /// Set a custom option type size range
+    Option *type_size(int option_type_size_min, int option_type_size_max);
 
     /// Set the value of the separator injection flag
     void inject_separator(bool value = true) { inject_separator_ = value; }
@@ -1162,7 +763,7 @@ class Option : public OptionBase<Option> {
             add_result(val_str);
             // if trigger_on_result_ is set the callback already ran
             if(run_callback_for_default_ && !trigger_on_result_) {
-                run_callback();  // run callback sets the state we need to reset it again
+                run_callback();  // run callback sets the state, we need to reset it again
                 current_option_state_ = option_state::parsing;
             } else {
                 _validate_results(results_);
@@ -1180,169 +781,27 @@ class Option : public OptionBase<Option> {
     }
 
     /// Get the full typename for this option
-    std::string get_type_name() const {
-        std::string full_type_name = type_name_();
-        if(!validators_.empty()) {
-            for(auto &Validator : validators_) {
-                std::string vtype = Validator.get_description();
-                if(!vtype.empty()) {
-                    full_type_name += ":" + vtype;
-                }
-            }
-        }
-        return full_type_name;
-    }
+    CLI11_NODISCARD std::string get_type_name() const;
 
   private:
     /// Run the results through the Validators
-    void _validate_results(results_t &res) const {
-        // Run the Validators (can change the string)
-        if(!validators_.empty()) {
-            if(type_size_max_ > 1) {  // in this context index refers to the index in the type
-                int index = 0;
-                if(get_items_expected_max() < static_cast<int>(res.size()) &&
-                   multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
-                    // create a negative index for the earliest ones
-                    index = get_items_expected_max() - static_cast<int>(res.size());
-                }
-
-                for(std::string &result : res) {
-                    if(detail::is_separator(result) && type_size_max_ != type_size_min_ && index >= 0) {
-                        index = 0;  // reset index for variable size chunks
-                        continue;
-                    }
-                    auto err_msg = _validate(result, (index >= 0) ? (index % type_size_max_) : index);
-                    if(!err_msg.empty())
-                        throw ValidationError(get_name(), err_msg);
-                    ++index;
-                }
-            } else {
-                int index = 0;
-                if(expected_max_ < static_cast<int>(res.size()) &&
-                   multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
-                    // create a negative index for the earliest ones
-                    index = expected_max_ - static_cast<int>(res.size());
-                }
-                for(std::string &result : res) {
-                    auto err_msg = _validate(result, index);
-                    ++index;
-                    if(!err_msg.empty())
-                        throw ValidationError(get_name(), err_msg);
-                }
-            }
-        }
-    }
+    void _validate_results(results_t &res) const;
 
     /** reduce the results in accordance with the MultiOptionPolicy
-    @param[out] res results are assigned to res if there if they are different
+    @param[out] out results are assigned to res if there if they are different
     */
-    void _reduce_results(results_t &res, const results_t &original) const {
-
-        // max num items expected or length of vector, always at least 1
-        // Only valid for a trimming policy
-
-        res.clear();
-        // Operation depends on the policy setting
-        switch(multi_option_policy_) {
-        case MultiOptionPolicy::TakeAll:
-            break;
-        case MultiOptionPolicy::TakeLast: {
-            // Allow multi-option sizes (including 0)
-            std::size_t trim_size = std::min<std::size_t>(
-                static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
-            if(original.size() != trim_size) {
-                res.assign(original.end() - static_cast<results_t::difference_type>(trim_size), original.end());
-            }
-        } break;
-        case MultiOptionPolicy::TakeFirst: {
-            std::size_t trim_size = std::min<std::size_t>(
-                static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
-            if(original.size() != trim_size) {
-                res.assign(original.begin(), original.begin() + static_cast<results_t::difference_type>(trim_size));
-            }
-        } break;
-        case MultiOptionPolicy::Join:
-            if(results_.size() > 1) {
-                res.push_back(detail::join(original, std::string(1, (delimiter_ == '\0') ? '\n' : delimiter_)));
-            }
-            break;
-        case MultiOptionPolicy::Throw:
-        default: {
-            auto num_min = static_cast<std::size_t>(get_items_expected_min());
-            auto num_max = static_cast<std::size_t>(get_items_expected_max());
-            if(num_min == 0) {
-                num_min = 1;
-            }
-            if(num_max == 0) {
-                num_max = 1;
-            }
-            if(original.size() < num_min) {
-                throw ArgumentMismatch::AtLeast(get_name(), static_cast<int>(num_min), original.size());
-            }
-            if(original.size() > num_max) {
-                throw ArgumentMismatch::AtMost(get_name(), static_cast<int>(num_max), original.size());
-            }
-            break;
-        }
-        }
-    }
+    void _reduce_results(results_t &out, const results_t &original) const;
 
     // Run a result through the Validators
-    std::string _validate(std::string &result, int index) const {
-        std::string err_msg;
-        if(result.empty() && expected_min_ == 0) {
-            // an empty with nothing expected is allowed
-            return err_msg;
-        }
-        for(const auto &vali : validators_) {
-            auto v = vali.get_application_index();
-            if(v == -1 || v == index) {
-                try {
-                    err_msg = vali(result);
-                } catch(const ValidationError &err) {
-                    err_msg = err.what();
-                }
-                if(!err_msg.empty())
-                    break;
-            }
-        }
-
-        return err_msg;
-    }
+    std::string _validate(std::string &result, int index) const;
 
     /// Add a single result to the result set, taking into account delimiters
-    int _add_result(std::string &&result, std::vector<std::string> &res) const {
-        int result_count = 0;
-        if(allow_extra_args_ && !result.empty() && result.front() == '[' &&
-           result.back() == ']') {  // this is now a vector string likely from the default or user entry
-            result.pop_back();
-
-            for(auto &var : CLI::detail::split(result.substr(1), ',')) {
-                if(!var.empty()) {
-                    result_count += _add_result(std::move(var), res);
-                }
-            }
-            return result_count;
-        }
-        if(delimiter_ == '\0') {
-            res.push_back(std::move(result));
-            ++result_count;
-        } else {
-            if((result.find_first_of(delimiter_) != std::string::npos)) {
-                for(const auto &var : CLI::detail::split(result, delimiter_)) {
-                    if(!var.empty()) {
-                        res.push_back(var);
-                        ++result_count;
-                    }
-                }
-            } else {
-                res.push_back(std::move(result));
-                ++result_count;
-            }
-        }
-        return result_count;
-    }
-};  // namespace CLI
+    int _add_result(std::string &&result, std::vector<std::string> &res) const;
+};
 
 // [CLI11:option_hpp:end]
 }  // namespace CLI
+
+#ifndef CLI11_COMPILE
+#include "impl/Option_inl.hpp"
+#endif
diff --git a/include/CLI/StringTools.hpp b/include/CLI/StringTools.hpp
index 4718aed..2a31005 100644
--- a/include/CLI/StringTools.hpp
+++ b/include/CLI/StringTools.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -17,6 +17,8 @@
 #include <vector>
 // [CLI11:public_includes:end]
 
+#include "Macros.hpp"
+
 namespace CLI {
 
 // [CLI11:string_tools_hpp:verbatim]
@@ -43,21 +45,7 @@ namespace detail {
 constexpr int expected_max_vector_size{1 << 29};
 // Based on http://stackoverflow.com/questions/236129/split-a-string-in-c
 /// Split a string by a delim
-inline std::vector<std::string> split(const std::string &s, char delim) {
-    std::vector<std::string> elems;
-    // Check to see if empty string, give consistent result
-    if(s.empty()) {
-        elems.emplace_back();
-    } else {
-        std::stringstream ss;
-        ss.str(s);
-        std::string item;
-        while(std::getline(ss, item, delim)) {
-            elems.push_back(item);
-        }
-    }
-    return elems;
-}
+CLI11_INLINE std::vector<std::string> split(const std::string &s, char delim);
 
 /// Simple function to join a string
 template <typename T> std::string join(const T &v, std::string delim = ",") {
@@ -106,33 +94,16 @@ template <typename T> std::string rjoin(const T &v, std::string delim = ",") {
 // Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string
 
 /// Trim whitespace from left of string
-inline std::string &ltrim(std::string &str) {
-    auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
-    str.erase(str.begin(), it);
-    return str;
-}
+CLI11_INLINE std::string &ltrim(std::string &str);
 
 /// Trim anything from left of string
-inline std::string &ltrim(std::string &str, const std::string &filter) {
-    auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
-    str.erase(str.begin(), it);
-    return str;
-}
+CLI11_INLINE std::string &ltrim(std::string &str, const std::string &filter);
 
 /// Trim whitespace from right of string
-inline std::string &rtrim(std::string &str) {
-    auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
-    str.erase(it.base(), str.end());
-    return str;
-}
+CLI11_INLINE std::string &rtrim(std::string &str);
 
 /// Trim anything from right of string
-inline std::string &rtrim(std::string &str, const std::string &filter) {
-    auto it =
-        std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
-    str.erase(it.base(), str.end());
-    return str;
-}
+CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter);
 
 /// Trim whitespace from string
 inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); }
@@ -147,31 +118,13 @@ inline std::string trim_copy(const std::string &str) {
 }
 
 /// remove quotes at the front and back of a string either '"' or '\''
-inline std::string &remove_quotes(std::string &str) {
-    if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) {
-        if(str.front() == str.back()) {
-            str.pop_back();
-            str.erase(str.begin(), str.begin() + 1);
-        }
-    }
-    return str;
-}
+CLI11_INLINE std::string &remove_quotes(std::string &str);
 
 /// Add a leader to the beginning of all new lines (nothing is added
 /// at the start of the first line). `"; "` would be for ini files
 ///
 /// Can't use Regex, or this would be a subs.
-inline std::string fix_newlines(const std::string &leader, std::string input) {
-    std::string::size_type n = 0;
-    while(n != std::string::npos && n < input.size()) {
-        n = input.find('\n', n);
-        if(n != std::string::npos) {
-            input = input.substr(0, n + 1) + leader + input.substr(n + 1);
-            n += leader.size();
-        }
-    }
-    return input;
-}
+CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input);
 
 /// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered)
 inline std::string trim_copy(const std::string &str, const std::string &filter) {
@@ -179,40 +132,11 @@ inline std::string trim_copy(const std::string &str, const std::string &filter)
     return trim(s, filter);
 }
 /// Print a two part "help" string
-inline std::ostream &format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) {
-    name = "  " + name;
-    out << std::setw(static_cast<int>(wid)) << std::left << name;
-    if(!description.empty()) {
-        if(name.length() >= wid)
-            out << "\n" << std::setw(static_cast<int>(wid)) << "";
-        for(const char c : description) {
-            out.put(c);
-            if(c == '\n') {
-                out << std::setw(static_cast<int>(wid)) << "";
-            }
-        }
-    }
-    out << "\n";
-    return out;
-}
+CLI11_INLINE std::ostream &
+format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid);
 
 /// Print subcommand aliases
-inline std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid) {
-    if(!aliases.empty()) {
-        out << std::setw(static_cast<int>(wid)) << "     aliases: ";
-        bool front = true;
-        for(const auto &alias : aliases) {
-            if(!front) {
-                out << ", ";
-            } else {
-                front = false;
-            }
-            out << detail::fix_newlines("              ", alias);
-        }
-        out << "\n";
-    }
-    return out;
-}
+CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid);
 
 /// Verify the first character of an option
 /// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with
@@ -227,16 +151,7 @@ template <typename T> bool valid_later_char(T c) {
 }
 
 /// Verify an option/subcommand name
-inline bool valid_name_string(const std::string &str) {
-    if(str.empty() || !valid_first_char(str[0])) {
-        return false;
-    }
-    auto e = str.end();
-    for(auto c = str.begin() + 1; c != e; ++c)
-        if(!valid_later_char(*c))
-            return false;
-    return true;
-}
+CLI11_INLINE bool valid_name_string(const std::string &str);
 
 /// Verify an app name
 inline bool valid_alias_name_string(const std::string &str) {
@@ -270,66 +185,20 @@ inline std::string remove_underscore(std::string str) {
 }
 
 /// Find and replace a substring with another substring
-inline std::string find_and_replace(std::string str, std::string from, std::string to) {
-
-    std::size_t start_pos = 0;
-
-    while((start_pos = str.find(from, start_pos)) != std::string::npos) {
-        str.replace(start_pos, from.length(), to);
-        start_pos += to.length();
-    }
-
-    return str;
-}
+CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to);
 
 /// check if the flag definitions has possible false flags
 inline bool has_default_flag_values(const std::string &flags) {
     return (flags.find_first_of("{!") != std::string::npos);
 }
 
-inline void remove_default_flag_values(std::string &flags) {
-    auto loc = flags.find_first_of('{', 2);
-    while(loc != std::string::npos) {
-        auto finish = flags.find_first_of("},", loc + 1);
-        if((finish != std::string::npos) && (flags[finish] == '}')) {
-            flags.erase(flags.begin() + static_cast<std::ptrdiff_t>(loc),
-                        flags.begin() + static_cast<std::ptrdiff_t>(finish) + 1);
-        }
-        loc = flags.find_first_of('{', loc + 1);
-    }
-    flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
-}
+CLI11_INLINE void remove_default_flag_values(std::string &flags);
 
 /// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores
-inline std::ptrdiff_t find_member(std::string name,
-                                  const std::vector<std::string> names,
-                                  bool ignore_case = false,
-                                  bool ignore_underscore = false) {
-    auto it = std::end(names);
-    if(ignore_case) {
-        if(ignore_underscore) {
-            name = detail::to_lower(detail::remove_underscore(name));
-            it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
-                return detail::to_lower(detail::remove_underscore(local_name)) == name;
-            });
-        } else {
-            name = detail::to_lower(name);
-            it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
-                return detail::to_lower(local_name) == name;
-            });
-        }
-
-    } else if(ignore_underscore) {
-        name = detail::remove_underscore(name);
-        it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
-            return detail::remove_underscore(local_name) == name;
-        });
-    } else {
-        it = std::find(std::begin(names), std::end(names), name);
-    }
-
-    return (it != std::end(names)) ? (it - std::begin(names)) : (-1);
-}
+CLI11_INLINE std::ptrdiff_t find_member(std::string name,
+                                        const std::vector<std::string> names,
+                                        bool ignore_case = false,
+                                        bool ignore_underscore = false);
 
 /// Find a trigger string and call a modify callable function that takes the current string and starting position of the
 /// trigger and returns the position in the string to search for the next trigger string
@@ -343,88 +212,23 @@ template <typename Callable> inline std::string find_and_modify(std::string str,
 
 /// Split a string '"one two" "three"' into 'one two', 'three'
 /// Quote characters can be ` ' or "
-inline std::vector<std::string> split_up(std::string str, char delimiter = '\0') {
-
-    const std::string delims("\'\"`");
-    auto find_ws = [delimiter](char ch) {
-        return (delimiter == '\0') ? (std::isspace<char>(ch, std::locale()) != 0) : (ch == delimiter);
-    };
-    trim(str);
-
-    std::vector<std::string> output;
-    bool embeddedQuote = false;
-    char keyChar = ' ';
-    while(!str.empty()) {
-        if(delims.find_first_of(str[0]) != std::string::npos) {
-            keyChar = str[0];
-            auto end = str.find_first_of(keyChar, 1);
-            while((end != std::string::npos) && (str[end - 1] == '\\')) {  // deal with escaped quotes
-                end = str.find_first_of(keyChar, end + 1);
-                embeddedQuote = true;
-            }
-            if(end != std::string::npos) {
-                output.push_back(str.substr(1, end - 1));
-                if(end + 2 < str.size()) {
-                    str = str.substr(end + 2);
-                } else {
-                    str.clear();
-                }
-
-            } else {
-                output.push_back(str.substr(1));
-                str = "";
-            }
-        } else {
-            auto it = std::find_if(std::begin(str), std::end(str), find_ws);
-            if(it != std::end(str)) {
-                std::string value = std::string(str.begin(), it);
-                output.push_back(value);
-                str = std::string(it + 1, str.end());
-            } else {
-                output.push_back(str);
-                str = "";
-            }
-        }
-        // transform any embedded quotes into the regular character
-        if(embeddedQuote) {
-            output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar));
-            embeddedQuote = false;
-        }
-        trim(str);
-    }
-    return output;
-}
+CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter = '\0');
 
 /// This function detects an equal or colon followed by an escaped quote after an argument
 /// then modifies the string to replace the equality with a space.  This is needed
 /// to allow the split up function to work properly and is intended to be used with the find_and_modify function
 /// the return value is the offset+1 which is required by the find_and_modify function.
-inline std::size_t escape_detect(std::string &str, std::size_t offset) {
-    auto next = str[offset + 1];
-    if((next == '\"') || (next == '\'') || (next == '`')) {
-        auto astart = str.find_last_of("-/ \"\'`", offset - 1);
-        if(astart != std::string::npos) {
-            if(str[astart] == ((str[offset] == '=') ? '-' : '/'))
-                str[offset] = ' ';  // interpret this as a space so the split_up works properly
-        }
-    }
-    return offset + 1;
-}
+CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset);
 
 /// Add quotes if the string contains spaces
-inline std::string &add_quotes_if_needed(std::string &str) {
-    if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) {
-        char quote = str.find('"') < str.find('\'') ? '\'' : '"';
-        if(str.find(' ') != std::string::npos) {
-            str.insert(0, 1, quote);
-            str.append(1, quote);
-        }
-    }
-    return str;
-}
+CLI11_INLINE std::string &add_quotes_if_needed(std::string &str);
 
 }  // namespace detail
 
 // [CLI11:string_tools_hpp:end]
 
 }  // namespace CLI
+
+#ifndef CLI11_COMPILE
+#include "impl/StringTools_inl.hpp"
+#endif
diff --git a/include/CLI/Timer.hpp b/include/CLI/Timer.hpp
index 429ca02..b185d33 100644
--- a/include/CLI/Timer.hpp
+++ b/include/CLI/Timer.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -12,6 +12,8 @@
 #define _GLIBCXX_USE_NANOSLEEP
 #endif
 
+#include <cmath>
+
 #include <array>
 #include <chrono>
 #include <functional>
@@ -63,7 +65,7 @@ class Timer {
     /// Time a function by running it multiple times. Target time is the len to target.
     std::string time_it(std::function<void()> f, double target_time = 1) {
         time_point start = start_;
-        double total_time;
+        double total_time = NAN;
 
         start_ = clock::now();
         std::size_t n = 0;
@@ -79,7 +81,7 @@ class Timer {
     }
 
     /// This formats the numerical value for the time string
-    std::string make_time_str() const {
+    std::string make_time_str() const {  // NOLINT(modernize-use-nodiscard)
         time_point stop = clock::now();
         std::chrono::duration<double> elapsed = stop - start_;
         double time = elapsed.count() / static_cast<double>(cycles);
@@ -88,7 +90,7 @@ class Timer {
 
     // LCOV_EXCL_START
     /// This prints out a time string from a time
-    std::string make_time_str(double time) const {
+    std::string make_time_str(double time) const {  // NOLINT(modernize-use-nodiscard)
         auto print_it = [](double x, std::string unit) {
             const unsigned int buffer_length = 50;
             std::array<char, buffer_length> buffer;
@@ -98,17 +100,16 @@ class Timer {
 
         if(time < .000001)
             return print_it(time * 1000000000, "ns");
-        else if(time < .001)
+        if(time < .001)
             return print_it(time * 1000000, "us");
-        else if(time < 1)
+        if(time < 1)
             return print_it(time * 1000, "ms");
-        else
-            return print_it(time, "s");
+        return print_it(time, "s");
     }
     // LCOV_EXCL_STOP
 
     /// This is the main function, it creates a string
-    std::string to_string() const { return time_print_(title_, make_time_str()); }
+    std::string to_string() const { return time_print_(title_, make_time_str()); }  // NOLINT(modernize-use-nodiscard)
 
     /// Division sets the number of cycles to divide by (no graphical change)
     Timer &operator/(std::size_t val) {
diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp
index 0fa2299..1555b29 100644
--- a/include/CLI/TypeTools.hpp
+++ b/include/CLI/TypeTools.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -7,8 +7,10 @@
 #pragma once
 
 // [CLI11:public_includes:set]
+#include <cmath>
 #include <cstdint>
 #include <exception>
+#include <limits>
 #include <memory>
 #include <string>
 #include <type_traits>
@@ -41,7 +43,9 @@ constexpr enabler dummy = {};
 template <bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type;
 
 /// A copy of std::void_t from C++17 (helper for C++11 and C++14)
-template <typename... Ts> struct make_void { using type = void; };
+template <typename... Ts> struct make_void {
+    using type = void;
+};
 
 /// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine
 template <typename... Ts> using void_t = typename make_void<Ts...>::type;
@@ -70,10 +74,14 @@ template <typename T> struct is_copyable_ptr {
 };
 
 /// This can be specialized to override the type deduction for IsMember.
-template <typename T> struct IsMemberType { using type = T; };
+template <typename T> struct IsMemberType {
+    using type = T;
+};
 
 /// The main custom type needed here is const char * should be a string.
-template <> struct IsMemberType<const char *> { using type = std::string; };
+template <> struct IsMemberType<const char *> {
+    using type = std::string;
+};
 
 namespace detail {
 
@@ -83,7 +91,9 @@ namespace detail {
 /// pointer_traits<T> be valid.
 
 /// not a pointer
-template <typename T, typename Enable = void> struct element_type { using type = T; };
+template <typename T, typename Enable = void> struct element_type {
+    using type = T;
+};
 
 template <typename T> struct element_type<T, typename std::enable_if<is_copyable_ptr<T>::value>::type> {
     using type = typename std::pointer_traits<T>::element_type;
@@ -91,7 +101,9 @@ template <typename T> struct element_type<T, typename std::enable_if<is_copyable
 
 /// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of
 /// the container
-template <typename T> struct element_value_type { using type = typename element_type<T>::type::value_type; };
+template <typename T> struct element_value_type {
+    using type = typename element_type<T>::type::value_type;
+};
 
 /// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing.
 template <typename T, typename _ = void> struct pair_adaptor : std::false_type {
@@ -148,7 +160,7 @@ template <typename T, typename C> class is_direct_constructible {
 #ifdef __CUDACC__
 #pragma diag_suppress 2361
 #endif
-        TT { std::declval<CC>() }
+        TT{std::declval<CC>()}
 #ifdef __CUDACC__
 #pragma diag_default 2361
 #endif
@@ -275,7 +287,7 @@ template <typename T,
           enable_if_t<std::is_constructible<std::string, T>::value && !std::is_convertible<T, std::string>::value,
                       detail::enabler> = detail::dummy>
 std::string to_string(const T &value) {
-    return std::string(value);
+    return std::string(value);  // NOLINT(google-readability-casting)
 }
 
 /// Convert an object to a string (streaming must be supported for that type)
@@ -295,7 +307,7 @@ template <typename T,
                           !is_readable_container<typename std::remove_const<T>::type>::value,
                       detail::enabler> = detail::dummy>
 std::string to_string(T &&) {
-    return std::string{};
+    return {};
 }
 
 /// convert a readable container to a string
@@ -304,14 +316,17 @@ template <typename T,
                           is_readable_container<T>::value,
                       detail::enabler> = detail::dummy>
 std::string to_string(T &&variable) {
-    std::vector<std::string> defaults;
     auto cval = variable.begin();
     auto end = variable.end();
+    if(cval == end) {
+        return {"{}"};
+    }
+    std::vector<std::string> defaults;
     while(cval != end) {
         defaults.emplace_back(CLI::detail::to_string(*cval));
         ++cval;
     }
-    return std::string("[" + detail::join(defaults) + "]");
+    return {"[" + detail::join(defaults) + "]"};
 }
 
 /// special template overload
@@ -349,7 +364,9 @@ auto value_string(const T &value) -> decltype(to_string(value)) {
 }
 
 /// template to get the underlying value type if it exists or use a default
-template <typename T, typename def, typename Enable = void> struct wrapped_type { using type = def; };
+template <typename T, typename def, typename Enable = void> struct wrapped_type {
+    using type = def;
+};
 
 /// Type size for regular object types that do not look like a tuple
 template <typename T, typename def> struct wrapped_type<T, def, typename std::enable_if<is_wrapper<T>::value>::type> {
@@ -357,7 +374,9 @@ template <typename T, typename def> struct wrapped_type<T, def, typename std::en
 };
 
 /// This will only trigger for actual void type
-template <typename T, typename Enable = void> struct type_count_base { static const int value{0}; };
+template <typename T, typename Enable = void> struct type_count_base {
+    static const int value{0};
+};
 
 /// Type size for regular object types that do not look like a tuple
 template <typename T>
@@ -387,7 +406,9 @@ template <typename T> struct subtype_count;
 template <typename T> struct subtype_count_min;
 
 /// This will only trigger for actual void type
-template <typename T, typename Enable = void> struct type_count { static const int value{0}; };
+template <typename T, typename Enable = void> struct type_count {
+    static const int value{0};
+};
 
 /// Type size for regular object types that do not look like a tuple
 template <typename T>
@@ -438,7 +459,9 @@ template <typename T> struct subtype_count {
 };
 
 /// This will only trigger for actual void type
-template <typename T, typename Enable = void> struct type_count_min { static const int value{0}; };
+template <typename T, typename Enable = void> struct type_count_min {
+    static const int value{0};
+};
 
 /// Type size for regular object types that do not look like a tuple
 template <typename T>
@@ -487,7 +510,9 @@ template <typename T> struct subtype_count_min {
 };
 
 /// This will only trigger for actual void type
-template <typename T, typename Enable = void> struct expected_count { static const int value{0}; };
+template <typename T, typename Enable = void> struct expected_count {
+    static const int value{0};
+};
 
 /// For most types the number of expected items is 1
 template <typename T>
@@ -651,7 +676,8 @@ struct classify_object<
     typename std::enable_if<is_tuple_like<T>::value &&
                             ((type_count<T>::value >= 2 && !is_wrapper<T>::value) ||
                              (uncommon_type<T>::value && !is_direct_constructible<T, double>::value &&
-                              !is_direct_constructible<T, int>::value))>::type> {
+                              !is_direct_constructible<T, int>::value) ||
+                             (uncommon_type<T>::value && type_count<T>::value >= 2))>::type> {
     static constexpr object_category value{object_category::tuple_value};
     // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be
     // constructed from just the first element so tuples of <string, int,int> can be constructed from a string, which
@@ -759,8 +785,8 @@ inline typename std::enable_if<I == type_count_base<T>::value, std::string>::typ
 /// Recursively generate the tuple type name
 template <typename T, std::size_t I>
 inline typename std::enable_if<(I < type_count_base<T>::value), std::string>::type tuple_name() {
-    std::string str = std::string(type_name<typename std::decay<typename std::tuple_element<I, T>::type>::type>()) +
-                      ',' + tuple_name<T, I + 1>();
+    auto str = std::string{type_name<typename std::decay<typename std::tuple_element<I, T>::type>::type>()} + ',' +
+               tuple_name<T, I + 1>();
     if(str.back() == ',')
         str.pop_back();
     return str;
@@ -790,13 +816,26 @@ inline std::string type_name() {
 /// Convert to an unsigned integral
 template <typename T, enable_if_t<std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
 bool integral_conversion(const std::string &input, T &output) noexcept {
-    if(input.empty()) {
+    if(input.empty() || input.front() == '-') {
         return false;
     }
     char *val = nullptr;
+    errno = 0;
     std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0);
+    if(errno == ERANGE) {
+        return false;
+    }
     output = static_cast<T>(output_ll);
-    return val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll;
+    if(val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll) {
+        return true;
+    }
+    val = nullptr;
+    std::int64_t output_sll = std::strtoll(input.c_str(), &val, 0);
+    if(val == (input.c_str() + input.size())) {
+        output = (output_sll < 0) ? static_cast<T>(0) : static_cast<T>(output_sll);
+        return (static_cast<std::int64_t>(output) == output_sll);
+    }
+    return false;
 }
 
 /// Convert to a signed integral
@@ -806,9 +845,21 @@ bool integral_conversion(const std::string &input, T &output) noexcept {
         return false;
     }
     char *val = nullptr;
+    errno = 0;
     std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0);
+    if(errno == ERANGE) {
+        return false;
+    }
     output = static_cast<T>(output_ll);
-    return val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll;
+    if(val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll) {
+        return true;
+    }
+    if(input == "true") {
+        // this is to deal with a few oddities with flags and wrapper int types
+        output = static_cast<T>(1);
+        return true;
+    }
+    return false;
 }
 
 /// Convert a flag into an integer value  typically binary flags
@@ -822,7 +873,7 @@ inline std::int64_t to_flag_value(std::string val) {
         return -1;
     }
     val = detail::to_lower(val);
-    std::int64_t ret;
+    std::int64_t ret = 0;
     if(val.size() == 1) {
         if(val[0] >= '1' && val[0] <= '9') {
             return (static_cast<std::int64_t>(val[0]) - '0');
@@ -915,18 +966,18 @@ bool lexical_cast(const std::string &input, T &output) {
     bool worked = false;
     auto nloc = str1.find_last_of("+-");
     if(nloc != std::string::npos && nloc > 0) {
-        worked = detail::lexical_cast(str1.substr(0, nloc), x);
+        worked = lexical_cast(str1.substr(0, nloc), x);
         str1 = str1.substr(nloc);
         if(str1.back() == 'i' || str1.back() == 'j')
             str1.pop_back();
-        worked = worked && detail::lexical_cast(str1, y);
+        worked = worked && lexical_cast(str1, y);
     } else {
         if(str1.back() == 'i' || str1.back() == 'j') {
             str1.pop_back();
-            worked = detail::lexical_cast(str1, y);
+            worked = lexical_cast(str1, y);
             x = XC{0};
         } else {
-            worked = detail::lexical_cast(str1, x);
+            worked = lexical_cast(str1, x);
             y = XC{0};
         }
     }
@@ -998,17 +1049,18 @@ template <
     typename T,
     enable_if_t<classify_object<T>::value == object_category::number_constructible, detail::enabler> = detail::dummy>
 bool lexical_cast(const std::string &input, T &output) {
-    int val;
+    int val = 0;
     if(integral_conversion(input, val)) {
         output = T(val);
         return true;
-    } else {
-        double dval;
-        if(lexical_cast(input, dval)) {
-            output = T{dval};
-            return true;
-        }
     }
+
+    double dval = 0.0;
+    if(lexical_cast(input, dval)) {
+        output = T{dval};
+        return true;
+    }
+
     return from_stream(input, output);
 }
 
@@ -1017,7 +1069,7 @@ template <
     typename T,
     enable_if_t<classify_object<T>::value == object_category::integer_constructible, detail::enabler> = detail::dummy>
 bool lexical_cast(const std::string &input, T &output) {
-    int val;
+    int val = 0;
     if(integral_conversion(input, val)) {
         output = T(val);
         return true;
@@ -1030,7 +1082,7 @@ template <
     typename T,
     enable_if_t<classify_object<T>::value == object_category::double_constructible, detail::enabler> = detail::dummy>
 bool lexical_cast(const std::string &input, T &output) {
-    double val;
+    double val = 0.0;
     if(lexical_cast(input, val)) {
         output = T{val};
         return true;
@@ -1043,7 +1095,7 @@ template <typename T,
           enable_if_t<classify_object<T>::value == object_category::other && std::is_assignable<T &, int>::value,
                       detail::enabler> = detail::dummy>
 bool lexical_cast(const std::string &input, T &output) {
-    int val;
+    int val = 0;
     if(integral_conversion(input, val)) {
 #ifdef _MSC_VER
 #pragma warning(push)
@@ -1131,7 +1183,7 @@ bool lexical_assign(const std::string &input, AssignTo &output) {
         output = 0;
         return true;
     }
-    int val;
+    int val = 0;
     if(lexical_cast(input, val)) {
         output = val;
         return true;
@@ -1146,7 +1198,7 @@ template <typename AssignTo,
                       detail::enabler> = detail::dummy>
 bool lexical_assign(const std::string &input, AssignTo &output) {
     ConvertTo val{};
-    bool parse_result = (!input.empty()) ? lexical_cast<ConvertTo>(input, val) : true;
+    bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true;
     if(parse_result) {
         output = val;
     }
@@ -1162,7 +1214,7 @@ template <
                 detail::enabler> = detail::dummy>
 bool lexical_assign(const std::string &input, AssignTo &output) {
     ConvertTo val{};
-    bool parse_result = input.empty() ? true : lexical_cast<ConvertTo>(input, val);
+    bool parse_result = input.empty() ? true : lexical_cast(input, val);
     if(parse_result) {
         output = AssignTo(val);  // use () form of constructor to allow some implicit conversions
     }
@@ -1208,6 +1260,13 @@ template <class AssignTo,
                       detail::enabler> = detail::dummy>
 bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
     output.erase(output.begin(), output.end());
+    if(strings.size() == 1 && strings[0] == "{}") {
+        return true;
+    }
+    bool skip_remaining = false;
+    if(strings.size() == 2 && strings[0] == "{}" && is_separator(strings[1])) {
+        skip_remaining = true;
+    }
     for(const auto &elem : strings) {
         typename AssignTo::value_type out;
         bool retval = lexical_assign<typename AssignTo::value_type, typename ConvertTo::value_type>(elem, out);
@@ -1215,6 +1274,9 @@ bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &outp
             return false;
         }
         output.insert(output.end(), std::move(out));
+        if(skip_remaining) {
+            break;
+        }
     }
     return (!output.empty());
 }
@@ -1230,14 +1292,13 @@ bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &outpu
         if(str1.back() == 'i' || str1.back() == 'j') {
             str1.pop_back();
         }
-        auto worked = detail::lexical_cast(strings[0], x) && detail::lexical_cast(str1, y);
+        auto worked = lexical_cast(strings[0], x) && lexical_cast(str1, y);
         if(worked) {
             output = ConvertTo{x, y};
         }
         return worked;
-    } else {
-        return lexical_assign<AssignTo, ConvertTo>(strings[0], output);
     }
+    return lexical_assign<AssignTo, ConvertTo>(strings[0], output);
 }
 
 /// Conversion to a vector type using a particular single type as the conversion type
@@ -1488,61 +1549,40 @@ bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &outpu
     return false;
 }
 
-/// Sum a vector of flag representations
-/// The flag vector produces a series of strings in a vector,  simple true is represented by a "1",  simple false is
-/// by
-/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
-/// common true and false strings then uses stoll to convert the rest for summing
-template <typename T, enable_if_t<std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
-void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
-    std::int64_t count{0};
-    for(auto &flag : flags) {
-        count += detail::to_flag_value(flag);
-    }
-    output = (count > 0) ? static_cast<T>(count) : T{0};
-}
-
-/// Sum a vector of flag representations
-/// The flag vector produces a series of strings in a vector,  simple true is represented by a "1",  simple false is
-/// by
-/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
-/// common true and false strings then uses stoll to convert the rest for summing
-template <typename T, enable_if_t<std::is_signed<T>::value, detail::enabler> = detail::dummy>
-void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
-    std::int64_t count{0};
-    for(auto &flag : flags) {
-        count += detail::to_flag_value(flag);
+/// Sum a vector of strings
+inline std::string sum_string_vector(const std::vector<std::string> &values) {
+    double val{0.0};
+    bool fail{false};
+    std::string output;
+    for(const auto &arg : values) {
+        double tv{0.0};
+        auto comp = lexical_cast(arg, tv);
+        if(!comp) {
+            try {
+                tv = static_cast<double>(detail::to_flag_value(arg));
+            } catch(const std::exception &) {
+                fail = true;
+                break;
+            }
+        }
+        val += tv;
     }
-    output = static_cast<T>(count);
-}
-
-#ifdef _MSC_VER
-#pragma warning(push)
-#pragma warning(disable : 4800)
-#endif
-// with Atomic<XX> this could produce a warning due to the conversion but if atomic gets here it is an old style so will
-// most likely still work
-
-/// Sum a vector of flag representations
-/// The flag vector produces a series of strings in a vector,  simple true is represented by a "1",  simple false is
-/// by
-/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
-/// common true and false strings then uses stoll to convert the rest for summing
-template <typename T,
-          enable_if_t<!std::is_signed<T>::value && !std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
-void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
-    std::int64_t count{0};
-    for(auto &flag : flags) {
-        count += detail::to_flag_value(flag);
+    if(fail) {
+        for(const auto &arg : values) {
+            output.append(arg);
+        }
+    } else {
+        if(val <= static_cast<double>((std::numeric_limits<std::int64_t>::min)()) ||
+           val >= static_cast<double>((std::numeric_limits<std::int64_t>::max)()) ||
+           std::ceil(val) == std::floor(val)) {
+            output = detail::value_string(static_cast<int64_t>(val));
+        } else {
+            output = detail::value_string(val);
+        }
     }
-    std::string out = detail::to_string(count);
-    lexical_cast(out, output);
+    return output;
 }
 
-#ifdef _MSC_VER
-#pragma warning(pop)
-#endif
-
 }  // namespace detail
 // [CLI11:type_tools_hpp:end]
 }  // namespace CLI
diff --git a/include/CLI/Validators.hpp b/include/CLI/Validators.hpp
index 03eb77b..28bdcb2 100644
--- a/include/CLI/Validators.hpp
+++ b/include/CLI/Validators.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -6,6 +6,7 @@
 
 #pragma once
 
+#include "Error.hpp"
 #include "Macros.hpp"
 #include "StringTools.hpp"
 #include "TypeTools.hpp"
@@ -32,6 +33,9 @@
 // Filesystem cannot be used if targeting macOS < 10.15
 #if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500
 #define CLI11_HAS_FILESYSTEM 0
+#elif defined(__wasi__)
+// As of wasi-sdk-14, filesystem is not implemented
+#define CLI11_HAS_FILESYSTEM 0
 #else
 #include <filesystem>
 #if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703
@@ -91,6 +95,9 @@ class Validator {
     /// specify that a validator should not modify the input
     bool non_modifying_{false};
 
+    Validator(std::string validator_desc, std::function<std::string(std::string &)> func)
+        : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(func)) {}
+
   public:
     Validator() = default;
     /// Construct a Validator with just the description string
@@ -106,18 +113,7 @@ class Validator {
     }
     /// This is the required operator for a Validator - provided to help
     /// users (CLI11 uses the member `func` directly)
-    std::string operator()(std::string &str) const {
-        std::string retstring;
-        if(active_) {
-            if(non_modifying_) {
-                std::string value = str;
-                retstring = func_(value);
-            } else {
-                retstring = func_(str);
-            }
-        }
-        return retstring;
-    }
+    std::string operator()(std::string &str) const;
 
     /// This is the required operator for a Validator - provided to help
     /// users (CLI11 uses the member `func` directly)
@@ -132,13 +128,10 @@ class Validator {
         return *this;
     }
     /// Specify the type string
-    Validator description(std::string validator_desc) const {
-        Validator newval(*this);
-        newval.desc_function_ = [validator_desc]() { return validator_desc; };
-        return newval;
-    }
+    CLI11_NODISCARD Validator description(std::string validator_desc) const;
+
     /// Generate type description information for the Validator
-    std::string get_description() const {
+    CLI11_NODISCARD std::string get_description() const {
         if(active_) {
             return desc_function_();
         }
@@ -150,20 +143,20 @@ class Validator {
         return *this;
     }
     /// Specify the type string
-    Validator name(std::string validator_name) const {
+    CLI11_NODISCARD Validator name(std::string validator_name) const {
         Validator newval(*this);
         newval.name_ = std::move(validator_name);
         return newval;
     }
     /// Get the name of the Validator
-    const std::string &get_name() const { return name_; }
+    CLI11_NODISCARD const std::string &get_name() const { return name_; }
     /// Specify whether the Validator is active or not
     Validator &active(bool active_val = true) {
         active_ = active_val;
         return *this;
     }
     /// Specify whether the Validator is active or not
-    Validator active(bool active_val = true) const {
+    CLI11_NODISCARD Validator active(bool active_val = true) const {
         Validator newval(*this);
         newval.active_ = active_val;
         return newval;
@@ -180,107 +173,33 @@ class Validator {
         return *this;
     }
     /// Specify the application index of a validator
-    Validator application_index(int app_index) const {
+    CLI11_NODISCARD Validator application_index(int app_index) const {
         Validator newval(*this);
         newval.application_index_ = app_index;
         return newval;
     }
     /// Get the current value of the application index
-    int get_application_index() const { return application_index_; }
+    CLI11_NODISCARD int get_application_index() const { return application_index_; }
     /// Get a boolean if the validator is active
-    bool get_active() const { return active_; }
+    CLI11_NODISCARD bool get_active() const { return active_; }
 
     /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input
-    bool get_modifying() const { return !non_modifying_; }
+    CLI11_NODISCARD bool get_modifying() const { return !non_modifying_; }
 
     /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the
     /// same.
-    Validator operator&(const Validator &other) const {
-        Validator newval;
-
-        newval._merge_description(*this, other, " AND ");
-
-        // Give references (will make a copy in lambda function)
-        const std::function<std::string(std::string & filename)> &f1 = func_;
-        const std::function<std::string(std::string & filename)> &f2 = other.func_;
-
-        newval.func_ = [f1, f2](std::string &input) {
-            std::string s1 = f1(input);
-            std::string s2 = f2(input);
-            if(!s1.empty() && !s2.empty())
-                return std::string("(") + s1 + ") AND (" + s2 + ")";
-            else
-                return s1 + s2;
-        };
-
-        newval.active_ = (active_ & other.active_);
-        newval.application_index_ = application_index_;
-        return newval;
-    }
+    Validator operator&(const Validator &other) const;
 
     /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the
     /// same.
-    Validator operator|(const Validator &other) const {
-        Validator newval;
-
-        newval._merge_description(*this, other, " OR ");
-
-        // Give references (will make a copy in lambda function)
-        const std::function<std::string(std::string &)> &f1 = func_;
-        const std::function<std::string(std::string &)> &f2 = other.func_;
-
-        newval.func_ = [f1, f2](std::string &input) {
-            std::string s1 = f1(input);
-            std::string s2 = f2(input);
-            if(s1.empty() || s2.empty())
-                return std::string();
-
-            return std::string("(") + s1 + ") OR (" + s2 + ")";
-        };
-        newval.active_ = (active_ & other.active_);
-        newval.application_index_ = application_index_;
-        return newval;
-    }
+    Validator operator|(const Validator &other) const;
 
     /// Create a validator that fails when a given validator succeeds
-    Validator operator!() const {
-        Validator newval;
-        const std::function<std::string()> &dfunc1 = desc_function_;
-        newval.desc_function_ = [dfunc1]() {
-            auto str = dfunc1();
-            return (!str.empty()) ? std::string("NOT ") + str : std::string{};
-        };
-        // Give references (will make a copy in lambda function)
-        const std::function<std::string(std::string & res)> &f1 = func_;
-
-        newval.func_ = [f1, dfunc1](std::string &test) -> std::string {
-            std::string s1 = f1(test);
-            if(s1.empty()) {
-                return std::string("check ") + dfunc1() + " succeeded improperly";
-            }
-            return std::string{};
-        };
-        newval.active_ = active_;
-        newval.application_index_ = application_index_;
-        return newval;
-    }
+    Validator operator!() const;
 
   private:
-    void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger) {
-
-        const std::function<std::string()> &dfunc1 = val1.desc_function_;
-        const std::function<std::string()> &dfunc2 = val2.desc_function_;
-
-        desc_function_ = [=]() {
-            std::string f1 = dfunc1();
-            std::string f2 = dfunc2();
-            if((f1.empty()) || (f2.empty())) {
-                return f1 + f2;
-            }
-            return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')';
-        };
-    }
-};  // namespace CLI
+    void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger);
+};
 
 /// Class wrapping some of the accessors of Validator
 class CustomValidator : public Validator {
@@ -294,132 +213,37 @@ namespace detail {
 /// CLI enumeration of different file types
 enum class path_type { nonexistent, file, directory };
 
-#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
 /// get the type of the path from a file name
-inline path_type check_path(const char *file) noexcept {
-    std::error_code ec;
-    auto stat = std::filesystem::status(file, ec);
-    if(ec) {
-        return path_type::nonexistent;
-    }
-    switch(stat.type()) {
-    case std::filesystem::file_type::none:
-    case std::filesystem::file_type::not_found:
-        return path_type::nonexistent;
-    case std::filesystem::file_type::directory:
-        return path_type::directory;
-    case std::filesystem::file_type::symlink:
-    case std::filesystem::file_type::block:
-    case std::filesystem::file_type::character:
-    case std::filesystem::file_type::fifo:
-    case std::filesystem::file_type::socket:
-    case std::filesystem::file_type::regular:
-    case std::filesystem::file_type::unknown:
-    default:
-        return path_type::file;
-    }
-}
-#else
-/// get the type of the path from a file name
-inline path_type check_path(const char *file) noexcept {
-#if defined(_MSC_VER)
-    struct __stat64 buffer;
-    if(_stat64(file, &buffer) == 0) {
-        return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file;
-    }
-#else
-    struct stat buffer;
-    if(stat(file, &buffer) == 0) {
-        return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file;
-    }
-#endif
-    return path_type::nonexistent;
-}
-#endif
+CLI11_INLINE path_type check_path(const char *file) noexcept;
+
 /// Check for an existing file (returns error message if check fails)
 class ExistingFileValidator : public Validator {
   public:
-    ExistingFileValidator() : Validator("FILE") {
-        func_ = [](std::string &filename) {
-            auto path_result = check_path(filename.c_str());
-            if(path_result == path_type::nonexistent) {
-                return "File does not exist: " + filename;
-            }
-            if(path_result == path_type::directory) {
-                return "File is actually a directory: " + filename;
-            }
-            return std::string();
-        };
-    }
+    ExistingFileValidator();
 };
 
 /// Check for an existing directory (returns error message if check fails)
 class ExistingDirectoryValidator : public Validator {
   public:
-    ExistingDirectoryValidator() : Validator("DIR") {
-        func_ = [](std::string &filename) {
-            auto path_result = check_path(filename.c_str());
-            if(path_result == path_type::nonexistent) {
-                return "Directory does not exist: " + filename;
-            }
-            if(path_result == path_type::file) {
-                return "Directory is actually a file: " + filename;
-            }
-            return std::string();
-        };
-    }
+    ExistingDirectoryValidator();
 };
 
 /// Check for an existing path
 class ExistingPathValidator : public Validator {
   public:
-    ExistingPathValidator() : Validator("PATH(existing)") {
-        func_ = [](std::string &filename) {
-            auto path_result = check_path(filename.c_str());
-            if(path_result == path_type::nonexistent) {
-                return "Path does not exist: " + filename;
-            }
-            return std::string();
-        };
-    }
+    ExistingPathValidator();
 };
 
 /// Check for an non-existing path
 class NonexistentPathValidator : public Validator {
   public:
-    NonexistentPathValidator() : Validator("PATH(non-existing)") {
-        func_ = [](std::string &filename) {
-            auto path_result = check_path(filename.c_str());
-            if(path_result != path_type::nonexistent) {
-                return "Path already exists: " + filename;
-            }
-            return std::string();
-        };
-    }
+    NonexistentPathValidator();
 };
 
 /// Validate the given string is a legal ipv4 address
 class IPV4Validator : public Validator {
   public:
-    IPV4Validator() : Validator("IPV4") {
-        func_ = [](std::string &ip_addr) {
-            auto result = CLI::detail::split(ip_addr, '.');
-            if(result.size() != 4) {
-                return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')';
-            }
-            int num;
-            for(const auto &var : result) {
-                bool retval = detail::lexical_cast(var, num);
-                if(!retval) {
-                    return std::string("Failed parsing number (") + var + ')';
-                }
-                if(num < 0 || num > 255) {
-                    return std::string("Each IP number must be between 0 and 255 ") + var;
-                }
-            }
-            return std::string();
-        };
-    }
+    IPV4Validator();
 };
 
 }  // namespace detail
@@ -444,21 +268,28 @@ const detail::IPV4Validator ValidIPV4;
 /// Validate the input as a particular type
 template <typename DesiredType> class TypeValidator : public Validator {
   public:
-    explicit TypeValidator(const std::string &validator_name) : Validator(validator_name) {
-        func_ = [](std::string &input_string) {
-            auto val = DesiredType();
-            if(!detail::lexical_cast(input_string, val)) {
-                return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>();
-            }
-            return std::string();
-        };
-    }
+    explicit TypeValidator(const std::string &validator_name)
+        : Validator(validator_name, [](std::string &input_string) {
+              using CLI::detail::lexical_cast;
+              auto val = DesiredType();
+              if(!lexical_cast(input_string, val)) {
+                  return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>();
+              }
+              return std::string();
+          }) {}
     TypeValidator() : TypeValidator(detail::type_name<DesiredType>()) {}
 };
 
 /// Check for a number
 const TypeValidator<double> Number("NUMBER");
 
+/// Modify a path if the file is a particular default location, can be used as Check or transform
+/// with the error return optionally disabled
+class FileOnDefaultPath : public Validator {
+  public:
+    explicit FileOnDefaultPath(std::string default_path, bool enableErrorReturn = true);
+};
+
 /// Produce a range (factory). Min and max are inclusive.
 class Range : public Validator {
   public:
@@ -475,12 +306,15 @@ class Range : public Validator {
         }
 
         func_ = [min_val, max_val](std::string &input) {
+            using CLI::detail::lexical_cast;
             T val;
-            bool converted = detail::lexical_cast(input, val);
-            if((!converted) || (val < min_val || val > max_val))
-                return std::string("Value ") + input + " not in range " + std::to_string(min_val) + " to " +
-                       std::to_string(max_val);
-
+            bool converted = lexical_cast(input, val);
+            if((!converted) || (val < min_val || val > max_val)) {
+                std::stringstream out;
+                out << "Value " << input << " not in range [";
+                out << min_val << " - " << max_val << "]";
+                return out.str();
+            }
             return std::string{};
         };
     }
@@ -494,7 +328,7 @@ class Range : public Validator {
 /// Check for a non negative number
 const Range NonNegativeNumber((std::numeric_limits<double>::max)(), "NONNEGATIVE");
 
-/// Check for a positive valued number (val>0.0), min() her is the smallest positive number
+/// Check for a positive valued number (val>0.0), <double>::min  here is the smallest positive number
 const Range PositiveNumber((std::numeric_limits<double>::min)(), (std::numeric_limits<double>::max)(), "POSITIVE");
 
 /// Produce a bounded range (factory). Min and max are inclusive.
@@ -510,8 +344,9 @@ class Bound : public Validator {
         description(out.str());
 
         func_ = [min_val, max_val](std::string &input) {
+            using CLI::detail::lexical_cast;
             T val;
-            bool converted = detail::lexical_cast(input, val);
+            bool converted = lexical_cast(input, val);
             if(!converted) {
                 return std::string("Value ") + input + " could not be converted";
             }
@@ -631,9 +466,8 @@ template <typename T>
 inline typename std::enable_if<std::is_signed<T>::value, T>::type overflowCheck(const T &a, const T &b) {
     if((a > 0) == (b > 0)) {
         return ((std::numeric_limits<T>::max)() / (std::abs)(a) < (std::abs)(b));
-    } else {
-        return ((std::numeric_limits<T>::min)() / (std::abs)(a) > -(std::abs)(b));
     }
+    return ((std::numeric_limits<T>::min)() / (std::abs)(a) > -(std::abs)(b));
 }
 /// Do a check for overflow on unsigned numbers
 template <typename T>
@@ -703,8 +537,9 @@ class IsMember : public Validator {
         // This is the function that validates
         // It stores a copy of the set pointer-like, so shared_ptr will stay alive
         func_ = [set, filter_fn](std::string &input) {
+            using CLI::detail::lexical_cast;
             local_item_t b;
-            if(!detail::lexical_cast(input, b)) {
+            if(!lexical_cast(input, b)) {
                 throw ValidationError(input);  // name is added later
             }
             if(filter_fn) {
@@ -771,8 +606,9 @@ class Transformer : public Validator {
         desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); };
 
         func_ = [mapping, filter_fn](std::string &input) {
+            using CLI::detail::lexical_cast;
             local_item_t b;
-            if(!detail::lexical_cast(input, b)) {
+            if(!lexical_cast(input, b)) {
                 return std::string();
                 // there is no possible way we can match anything in the mapping if we can't convert so just return
             }
@@ -840,8 +676,9 @@ class CheckedTransformer : public Validator {
         desc_function_ = tfunc;
 
         func_ = [mapping, tfunc, filter_fn](std::string &input) {
+            using CLI::detail::lexical_cast;
             local_item_t b;
-            bool converted = detail::lexical_cast(input, b);
+            bool converted = lexical_cast(input, b);
             if(converted) {
                 if(filter_fn) {
                     b = filter_fn(b);
@@ -919,7 +756,7 @@ class AsNumberWithUnit : public Validator {
 
         // transform function
         func_ = [mapping, opts](std::string &input) -> std::string {
-            Number num;
+            Number num{};
 
             detail::rtrim(input);
             if(input.empty()) {
@@ -943,7 +780,8 @@ class AsNumberWithUnit : public Validator {
                 unit = detail::to_lower(unit);
             }
             if(unit.empty()) {
-                if(!detail::lexical_cast(input, num)) {
+                using CLI::detail::lexical_cast;
+                if(!lexical_cast(input, num)) {
                     throw ValidationError(std::string("Value ") + input + " could not be converted to " +
                                           detail::type_name<Number>());
                 }
@@ -961,7 +799,8 @@ class AsNumberWithUnit : public Validator {
             }
 
             if(!input.empty()) {
-                bool converted = detail::lexical_cast(input, num);
+                using CLI::detail::lexical_cast;
+                bool converted = lexical_cast(input, num);
                 if(!converted) {
                     throw ValidationError(std::string("Value ") + input + " could not be converted to " +
                                           detail::type_name<Number>());
@@ -1023,6 +862,10 @@ class AsNumberWithUnit : public Validator {
     }
 };
 
+inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, const AsNumberWithUnit::Options &b) {
+    return static_cast<AsNumberWithUnit::Options>(static_cast<int>(a) | static_cast<int>(b));
+}
+
 /// Converts a human-readable size string (with unit literal) to uin64_t size.
 /// Example:
 ///   "100" => 100
@@ -1045,44 +888,14 @@ class AsSizeValue : public AsNumberWithUnit {
     /// The first option is formally correct, but
     /// the second interpretation is more wide-spread
     /// (see https://en.wikipedia.org/wiki/Binary_prefix).
-    explicit AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) {
-        if(kb_is_1000) {
-            description("SIZE [b, kb(=1000b), kib(=1024b), ...]");
-        } else {
-            description("SIZE [b, kb(=1024b), ...]");
-        }
-    }
+    explicit AsSizeValue(bool kb_is_1000);
 
   private:
     /// Get <size unit, factor> mapping
-    static std::map<std::string, result_t> init_mapping(bool kb_is_1000) {
-        std::map<std::string, result_t> m;
-        result_t k_factor = kb_is_1000 ? 1000 : 1024;
-        result_t ki_factor = 1024;
-        result_t k = 1;
-        result_t ki = 1;
-        m["b"] = 1;
-        for(std::string p : {"k", "m", "g", "t", "p", "e"}) {
-            k *= k_factor;
-            ki *= ki_factor;
-            m[p] = k;
-            m[p + "b"] = k;
-            m[p + "i"] = ki;
-            m[p + "ib"] = ki;
-        }
-        return m;
-    }
+    static std::map<std::string, result_t> init_mapping(bool kb_is_1000);
 
     /// Cache calculated mapping
-    static std::map<std::string, result_t> get_mapping(bool kb_is_1000) {
-        if(kb_is_1000) {
-            static auto m = init_mapping(true);
-            return m;
-        } else {
-            static auto m = init_mapping(false);
-            return m;
-        }
-    }
+    static std::map<std::string, result_t> get_mapping(bool kb_is_1000);
 };
 
 namespace detail {
@@ -1090,53 +903,14 @@ namespace detail {
 /// the string is assumed to contain a file name followed by other arguments
 /// the return value contains is a pair with the first argument containing the program name and the second
 /// everything else.
-inline std::pair<std::string, std::string> split_program_name(std::string commandline) {
-    // try to determine the programName
-    std::pair<std::string, std::string> vals;
-    trim(commandline);
-    auto esp = commandline.find_first_of(' ', 1);
-    while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) {
-        esp = commandline.find_first_of(' ', esp + 1);
-        if(esp == std::string::npos) {
-            // if we have reached the end and haven't found a valid file just assume the first argument is the
-            // program name
-            if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') {
-                bool embeddedQuote = false;
-                auto keyChar = commandline[0];
-                auto end = commandline.find_first_of(keyChar, 1);
-                while((end != std::string::npos) && (commandline[end - 1] == '\\')) {  // deal with escaped quotes
-                    end = commandline.find_first_of(keyChar, end + 1);
-                    embeddedQuote = true;
-                }
-                if(end != std::string::npos) {
-                    vals.first = commandline.substr(1, end - 1);
-                    esp = end + 1;
-                    if(embeddedQuote) {
-                        vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar));
-                    }
-                } else {
-                    esp = commandline.find_first_of(' ', 1);
-                }
-            } else {
-                esp = commandline.find_first_of(' ', 1);
-            }
-
-            break;
-        }
-    }
-    if(vals.first.empty()) {
-        vals.first = commandline.substr(0, esp);
-        rtrim(vals.first);
-    }
-
-    // strip the program name
-    vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{};
-    ltrim(vals.second);
-    return vals;
-}
+CLI11_INLINE std::pair<std::string, std::string> split_program_name(std::string commandline);
 
 }  // namespace detail
 /// @}
 
 // [CLI11:validators_hpp:end]
 }  // namespace CLI
+
+#ifndef CLI11_COMPILE
+#include "impl/Validators_inl.hpp"
+#endif
diff --git a/include/CLI/Version.hpp b/include/CLI/Version.hpp
index 4bc79a7..d5c817a 100644
--- a/include/CLI/Version.hpp
+++ b/include/CLI/Version.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -9,8 +9,8 @@
 // [CLI11:version_hpp:verbatim]
 
 #define CLI11_VERSION_MAJOR 2
-#define CLI11_VERSION_MINOR 1
+#define CLI11_VERSION_MINOR 3
 #define CLI11_VERSION_PATCH 2
-#define CLI11_VERSION "2.1.2"
+#define CLI11_VERSION "2.3.2"
 
 // [CLI11:version_hpp:end]
diff --git a/include/CLI/impl/App_inl.hpp b/include/CLI/impl/App_inl.hpp
new file mode 100644
index 0000000..71afaa5
--- /dev/null
+++ b/include/CLI/impl/App_inl.hpp
@@ -0,0 +1,2105 @@
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// This include is only needed for IDEs to discover symbols
+#include <CLI/App.hpp>
+
+// [CLI11:public_includes:set]
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+namespace CLI {
+// [CLI11:app_inl_hpp:verbatim]
+
+CLI11_INLINE App::App(std::string app_description, std::string app_name, App *parent)
+    : name_(std::move(app_name)), description_(std::move(app_description)), parent_(parent) {
+    // Inherit if not from a nullptr
+    if(parent_ != nullptr) {
+        if(parent_->help_ptr_ != nullptr)
+            set_help_flag(parent_->help_ptr_->get_name(false, true), parent_->help_ptr_->get_description());
+        if(parent_->help_all_ptr_ != nullptr)
+            set_help_all_flag(parent_->help_all_ptr_->get_name(false, true), parent_->help_all_ptr_->get_description());
+
+        /// OptionDefaults
+        option_defaults_ = parent_->option_defaults_;
+
+        // INHERITABLE
+        failure_message_ = parent_->failure_message_;
+        allow_extras_ = parent_->allow_extras_;
+        allow_config_extras_ = parent_->allow_config_extras_;
+        prefix_command_ = parent_->prefix_command_;
+        immediate_callback_ = parent_->immediate_callback_;
+        ignore_case_ = parent_->ignore_case_;
+        ignore_underscore_ = parent_->ignore_underscore_;
+        fallthrough_ = parent_->fallthrough_;
+        validate_positionals_ = parent_->validate_positionals_;
+        validate_optional_arguments_ = parent_->validate_optional_arguments_;
+        configurable_ = parent_->configurable_;
+        allow_windows_style_options_ = parent_->allow_windows_style_options_;
+        group_ = parent_->group_;
+        footer_ = parent_->footer_;
+        formatter_ = parent_->formatter_;
+        config_formatter_ = parent_->config_formatter_;
+        require_subcommand_max_ = parent_->require_subcommand_max_;
+    }
+}
+
+CLI11_INLINE App *App::name(std::string app_name) {
+
+    if(parent_ != nullptr) {
+        auto oname = name_;
+        name_ = app_name;
+        const auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent());
+        if(!res.empty()) {
+            name_ = oname;
+            throw(OptionAlreadyAdded(app_name + " conflicts with existing subcommand names"));
+        }
+    } else {
+        name_ = app_name;
+    }
+    has_automatic_name_ = false;
+    return this;
+}
+
+CLI11_INLINE App *App::alias(std::string app_name) {
+    if(app_name.empty() || !detail::valid_alias_name_string(app_name)) {
+        throw IncorrectConstruction("Aliases may not be empty or contain newlines or null characters");
+    }
+    if(parent_ != nullptr) {
+        aliases_.push_back(app_name);
+        const auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent());
+        if(!res.empty()) {
+            aliases_.pop_back();
+            throw(OptionAlreadyAdded("alias already matches an existing subcommand: " + app_name));
+        }
+    } else {
+        aliases_.push_back(app_name);
+    }
+
+    return this;
+}
+
+CLI11_INLINE App *App::immediate_callback(bool immediate) {
+    immediate_callback_ = immediate;
+    if(immediate_callback_) {
+        if(final_callback_ && !(parse_complete_callback_)) {
+            std::swap(final_callback_, parse_complete_callback_);
+        }
+    } else if(!(final_callback_) && parse_complete_callback_) {
+        std::swap(final_callback_, parse_complete_callback_);
+    }
+    return this;
+}
+
+CLI11_INLINE App *App::ignore_case(bool value) {
+    if(value && !ignore_case_) {
+        ignore_case_ = true;
+        auto *p = (parent_ != nullptr) ? _get_fallthrough_parent() : this;
+        const auto &match = _compare_subcommand_names(*this, *p);
+        if(!match.empty()) {
+            ignore_case_ = false;  // we are throwing so need to be exception invariant
+            throw OptionAlreadyAdded("ignore case would cause subcommand name conflicts: " + match);
+        }
+    }
+    ignore_case_ = value;
+    return this;
+}
+
+CLI11_INLINE App *App::ignore_underscore(bool value) {
+    if(value && !ignore_underscore_) {
+        ignore_underscore_ = true;
+        auto *p = (parent_ != nullptr) ? _get_fallthrough_parent() : this;
+        const auto &match = _compare_subcommand_names(*this, *p);
+        if(!match.empty()) {
+            ignore_underscore_ = false;
+            throw OptionAlreadyAdded("ignore underscore would cause subcommand name conflicts: " + match);
+        }
+    }
+    ignore_underscore_ = value;
+    return this;
+}
+
+CLI11_INLINE Option *App::add_option(std::string option_name,
+                                     callback_t option_callback,
+                                     std::string option_description,
+                                     bool defaulted,
+                                     std::function<std::string()> func) {
+    Option myopt{option_name, option_description, option_callback, this};
+
+    if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) { return *v == myopt; }) ==
+       std::end(options_)) {
+        options_.emplace_back();
+        Option_p &option = options_.back();
+        option.reset(new Option(option_name, option_description, option_callback, this));
+
+        // Set the default string capture function
+        option->default_function(func);
+
+        // For compatibility with CLI11 1.7 and before, capture the default string here
+        if(defaulted)
+            option->capture_default_str();
+
+        // Transfer defaults to the new option
+        option_defaults_.copy_to(option.get());
+
+        // Don't bother to capture if we already did
+        if(!defaulted && option->get_always_capture_default())
+            option->capture_default_str();
+
+        return option.get();
+    }
+    // we know something matches now find what it is so we can produce more error information
+    for(auto &opt : options_) {
+        const auto &matchname = opt->matching_name(myopt);
+        if(!matchname.empty()) {
+            throw(OptionAlreadyAdded("added option matched existing option name: " + matchname));
+        }
+    }
+    // this line should not be reached the above loop should trigger the throw
+    throw(OptionAlreadyAdded("added option matched existing option name"));  // LCOV_EXCL_LINE
+}
+
+CLI11_INLINE Option *App::set_help_flag(std::string flag_name, const std::string &help_description) {
+    // take flag_description by const reference otherwise add_flag tries to assign to help_description
+    if(help_ptr_ != nullptr) {
+        remove_option(help_ptr_);
+        help_ptr_ = nullptr;
+    }
+
+    // Empty name will simply remove the help flag
+    if(!flag_name.empty()) {
+        help_ptr_ = add_flag(flag_name, help_description);
+        help_ptr_->configurable(false);
+    }
+
+    return help_ptr_;
+}
+
+CLI11_INLINE Option *App::set_help_all_flag(std::string help_name, const std::string &help_description) {
+    // take flag_description by const reference otherwise add_flag tries to assign to flag_description
+    if(help_all_ptr_ != nullptr) {
+        remove_option(help_all_ptr_);
+        help_all_ptr_ = nullptr;
+    }
+
+    // Empty name will simply remove the help all flag
+    if(!help_name.empty()) {
+        help_all_ptr_ = add_flag(help_name, help_description);
+        help_all_ptr_->configurable(false);
+    }
+
+    return help_all_ptr_;
+}
+
+CLI11_INLINE Option *
+App::set_version_flag(std::string flag_name, const std::string &versionString, const std::string &version_help) {
+    // take flag_description by const reference otherwise add_flag tries to assign to version_description
+    if(version_ptr_ != nullptr) {
+        remove_option(version_ptr_);
+        version_ptr_ = nullptr;
+    }
+
+    // Empty name will simply remove the version flag
+    if(!flag_name.empty()) {
+        version_ptr_ = add_flag_callback(
+            flag_name, [versionString]() { throw(CLI::CallForVersion(versionString, 0)); }, version_help);
+        version_ptr_->configurable(false);
+    }
+
+    return version_ptr_;
+}
+
+CLI11_INLINE Option *
+App::set_version_flag(std::string flag_name, std::function<std::string()> vfunc, const std::string &version_help) {
+    if(version_ptr_ != nullptr) {
+        remove_option(version_ptr_);
+        version_ptr_ = nullptr;
+    }
+
+    // Empty name will simply remove the version flag
+    if(!flag_name.empty()) {
+        version_ptr_ = add_flag_callback(
+            flag_name, [vfunc]() { throw(CLI::CallForVersion(vfunc(), 0)); }, version_help);
+        version_ptr_->configurable(false);
+    }
+
+    return version_ptr_;
+}
+
+CLI11_INLINE Option *App::_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description) {
+    Option *opt = nullptr;
+    if(detail::has_default_flag_values(flag_name)) {
+        // check for default values and if it has them
+        auto flag_defaults = detail::get_default_flag_values(flag_name);
+        detail::remove_default_flag_values(flag_name);
+        opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false);
+        for(const auto &fname : flag_defaults)
+            opt->fnames_.push_back(fname.first);
+        opt->default_flag_values_ = std::move(flag_defaults);
+    } else {
+        opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false);
+    }
+    // flags cannot have positional values
+    if(opt->get_positional()) {
+        auto pos_name = opt->get_name(true);
+        remove_option(opt);
+        throw IncorrectConstruction::PositionalFlag(pos_name);
+    }
+    opt->multi_option_policy(MultiOptionPolicy::TakeLast);
+    opt->expected(0);
+    opt->required(false);
+    return opt;
+}
+
+CLI11_INLINE Option *App::add_flag_callback(std::string flag_name,
+                                            std::function<void(void)> function,  ///< A function to call, void(void)
+                                            std::string flag_description) {
+
+    CLI::callback_t fun = [function](const CLI::results_t &res) {
+        using CLI::detail::lexical_cast;
+        bool trigger{false};
+        auto result = lexical_cast(res[0], trigger);
+        if(result && trigger) {
+            function();
+        }
+        return result;
+    };
+    return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
+}
+
+CLI11_INLINE Option *
+App::add_flag_function(std::string flag_name,
+                       std::function<void(std::int64_t)> function,  ///< A function to call, void(int)
+                       std::string flag_description) {
+
+    CLI::callback_t fun = [function](const CLI::results_t &res) {
+        using CLI::detail::lexical_cast;
+        std::int64_t flag_count{0};
+        lexical_cast(res[0], flag_count);
+        function(flag_count);
+        return true;
+    };
+    return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))
+        ->multi_option_policy(MultiOptionPolicy::Sum);
+}
+
+CLI11_INLINE Option *App::set_config(std::string option_name,
+                                     std::string default_filename,
+                                     const std::string &help_message,
+                                     bool config_required) {
+
+    // Remove existing config if present
+    if(config_ptr_ != nullptr) {
+        remove_option(config_ptr_);
+        config_ptr_ = nullptr;  // need to remove the config_ptr completely
+    }
+
+    // Only add config if option passed
+    if(!option_name.empty()) {
+        config_ptr_ = add_option(option_name, help_message);
+        if(config_required) {
+            config_ptr_->required();
+        }
+        if(!default_filename.empty()) {
+            config_ptr_->default_str(std::move(default_filename));
+        }
+        config_ptr_->configurable(false);
+    }
+
+    return config_ptr_;
+}
+
+CLI11_INLINE bool App::remove_option(Option *opt) {
+    // Make sure no links exist
+    for(Option_p &op : options_) {
+        op->remove_needs(opt);
+        op->remove_excludes(opt);
+    }
+
+    if(help_ptr_ == opt)
+        help_ptr_ = nullptr;
+    if(help_all_ptr_ == opt)
+        help_all_ptr_ = nullptr;
+
+    auto iterator =
+        std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; });
+    if(iterator != std::end(options_)) {
+        options_.erase(iterator);
+        return true;
+    }
+    return false;
+}
+
+CLI11_INLINE App *App::add_subcommand(std::string subcommand_name, std::string subcommand_description) {
+    if(!subcommand_name.empty() && !detail::valid_name_string(subcommand_name)) {
+        if(!detail::valid_first_char(subcommand_name[0])) {
+            throw IncorrectConstruction("Subcommand name starts with invalid character, '!' and '-' are not allowed");
+        }
+        for(auto c : subcommand_name) {
+            if(!detail::valid_later_char(c)) {
+                throw IncorrectConstruction(std::string("Subcommand name contains invalid character ('") + c +
+                                            "'), all characters are allowed except"
+                                            "'=',':','{','}', and ' '");
+            }
+        }
+    }
+    CLI::App_p subcom = std::shared_ptr<App>(new App(std::move(subcommand_description), subcommand_name, this));
+    return add_subcommand(std::move(subcom));
+}
+
+CLI11_INLINE App *App::add_subcommand(CLI::App_p subcom) {
+    if(!subcom)
+        throw IncorrectConstruction("passed App is not valid");
+    auto *ckapp = (name_.empty() && parent_ != nullptr) ? _get_fallthrough_parent() : this;
+    const auto &mstrg = _compare_subcommand_names(*subcom, *ckapp);
+    if(!mstrg.empty()) {
+        throw(OptionAlreadyAdded("subcommand name or alias matches existing subcommand: " + mstrg));
+    }
+    subcom->parent_ = this;
+    subcommands_.push_back(std::move(subcom));
+    return subcommands_.back().get();
+}
+
+CLI11_INLINE bool App::remove_subcommand(App *subcom) {
+    // Make sure no links exist
+    for(App_p &sub : subcommands_) {
+        sub->remove_excludes(subcom);
+        sub->remove_needs(subcom);
+    }
+
+    auto iterator = std::find_if(
+        std::begin(subcommands_), std::end(subcommands_), [subcom](const App_p &v) { return v.get() == subcom; });
+    if(iterator != std::end(subcommands_)) {
+        subcommands_.erase(iterator);
+        return true;
+    }
+    return false;
+}
+
+CLI11_INLINE App *App::get_subcommand(const App *subcom) const {
+    if(subcom == nullptr)
+        throw OptionNotFound("nullptr passed");
+    for(const App_p &subcomptr : subcommands_)
+        if(subcomptr.get() == subcom)
+            return subcomptr.get();
+    throw OptionNotFound(subcom->get_name());
+}
+
+CLI11_NODISCARD CLI11_INLINE App *App::get_subcommand(std::string subcom) const {
+    auto *subc = _find_subcommand(subcom, false, false);
+    if(subc == nullptr)
+        throw OptionNotFound(subcom);
+    return subc;
+}
+
+CLI11_NODISCARD CLI11_INLINE App *App::get_subcommand(int index) const {
+    if(index >= 0) {
+        auto uindex = static_cast<unsigned>(index);
+        if(uindex < subcommands_.size())
+            return subcommands_[uindex].get();
+    }
+    throw OptionNotFound(std::to_string(index));
+}
+
+CLI11_INLINE CLI::App_p App::get_subcommand_ptr(App *subcom) const {
+    if(subcom == nullptr)
+        throw OptionNotFound("nullptr passed");
+    for(const App_p &subcomptr : subcommands_)
+        if(subcomptr.get() == subcom)
+            return subcomptr;
+    throw OptionNotFound(subcom->get_name());
+}
+
+CLI11_NODISCARD CLI11_INLINE CLI::App_p App::get_subcommand_ptr(std::string subcom) const {
+    for(const App_p &subcomptr : subcommands_)
+        if(subcomptr->check_name(subcom))
+            return subcomptr;
+    throw OptionNotFound(subcom);
+}
+
+CLI11_NODISCARD CLI11_INLINE CLI::App_p App::get_subcommand_ptr(int index) const {
+    if(index >= 0) {
+        auto uindex = static_cast<unsigned>(index);
+        if(uindex < subcommands_.size())
+            return subcommands_[uindex];
+    }
+    throw OptionNotFound(std::to_string(index));
+}
+
+CLI11_NODISCARD CLI11_INLINE CLI::App *App::get_option_group(std::string group_name) const {
+    for(const App_p &app : subcommands_) {
+        if(app->name_.empty() && app->group_ == group_name) {
+            return app.get();
+        }
+    }
+    throw OptionNotFound(group_name);
+}
+
+CLI11_NODISCARD CLI11_INLINE std::size_t App::count_all() const {
+    std::size_t cnt{0};
+    for(const auto &opt : options_) {
+        cnt += opt->count();
+    }
+    for(const auto &sub : subcommands_) {
+        cnt += sub->count_all();
+    }
+    if(!get_name().empty()) {  // for named subcommands add the number of times the subcommand was called
+        cnt += parsed_;
+    }
+    return cnt;
+}
+
+CLI11_INLINE void App::clear() {
+
+    parsed_ = 0;
+    pre_parse_called_ = false;
+
+    missing_.clear();
+    parsed_subcommands_.clear();
+    for(const Option_p &opt : options_) {
+        opt->clear();
+    }
+    for(const App_p &subc : subcommands_) {
+        subc->clear();
+    }
+}
+
+CLI11_INLINE void App::parse(int argc, const char *const *argv) {
+    // If the name is not set, read from command line
+    if(name_.empty() || has_automatic_name_) {
+        has_automatic_name_ = true;
+        name_ = argv[0];
+    }
+
+    std::vector<std::string> args;
+    args.reserve(static_cast<std::size_t>(argc) - 1U);
+    for(auto i = static_cast<std::size_t>(argc) - 1U; i > 0U; --i)
+        args.emplace_back(argv[i]);
+    parse(std::move(args));
+}
+
+CLI11_INLINE void App::parse(std::string commandline, bool program_name_included) {
+
+    if(program_name_included) {
+        auto nstr = detail::split_program_name(commandline);
+        if((name_.empty()) || (has_automatic_name_)) {
+            has_automatic_name_ = true;
+            name_ = nstr.first;
+        }
+        commandline = std::move(nstr.second);
+    } else {
+        detail::trim(commandline);
+    }
+    // the next section of code is to deal with quoted arguments after an '=' or ':' for windows like operations
+    if(!commandline.empty()) {
+        commandline = detail::find_and_modify(commandline, "=", detail::escape_detect);
+        if(allow_windows_style_options_)
+            commandline = detail::find_and_modify(commandline, ":", detail::escape_detect);
+    }
+
+    auto args = detail::split_up(std::move(commandline));
+    // remove all empty strings
+    args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end());
+    std::reverse(args.begin(), args.end());
+
+    parse(std::move(args));
+}
+
+CLI11_INLINE void App::parse(std::vector<std::string> &args) {
+    // Clear if parsed
+    if(parsed_ > 0)
+        clear();
+
+    // parsed_ is incremented in commands/subcommands,
+    // but placed here to make sure this is cleared when
+    // running parse after an error is thrown, even by _validate or _configure.
+    parsed_ = 1;
+    _validate();
+    _configure();
+    // set the parent as nullptr as this object should be the top now
+    parent_ = nullptr;
+    parsed_ = 0;
+
+    _parse(args);
+    run_callback();
+}
+
+CLI11_INLINE void App::parse(std::vector<std::string> &&args) {
+    // Clear if parsed
+    if(parsed_ > 0)
+        clear();
+
+    // parsed_ is incremented in commands/subcommands,
+    // but placed here to make sure this is cleared when
+    // running parse after an error is thrown, even by _validate or _configure.
+    parsed_ = 1;
+    _validate();
+    _configure();
+    // set the parent as nullptr as this object should be the top now
+    parent_ = nullptr;
+    parsed_ = 0;
+
+    _parse(std::move(args));
+    run_callback();
+}
+
+CLI11_INLINE void App::parse_from_stream(std::istream &input) {
+    if(parsed_ == 0) {
+        _validate();
+        _configure();
+        // set the parent as nullptr as this object should be the top now
+    }
+
+    _parse_stream(input);
+    run_callback();
+}
+
+CLI11_INLINE int App::exit(const Error &e, std::ostream &out, std::ostream &err) const {
+
+    /// Avoid printing anything if this is a CLI::RuntimeError
+    if(e.get_name() == "RuntimeError")
+        return e.get_exit_code();
+
+    if(e.get_name() == "CallForHelp") {
+        out << help();
+        return e.get_exit_code();
+    }
+
+    if(e.get_name() == "CallForAllHelp") {
+        out << help("", AppFormatMode::All);
+        return e.get_exit_code();
+    }
+
+    if(e.get_name() == "CallForVersion") {
+        out << e.what() << std::endl;
+        return e.get_exit_code();
+    }
+
+    if(e.get_exit_code() != static_cast<int>(ExitCodes::Success)) {
+        if(failure_message_)
+            err << failure_message_(this, e) << std::flush;
+    }
+
+    return e.get_exit_code();
+}
+
+CLI11_INLINE std::vector<const App *> App::get_subcommands(const std::function<bool(const App *)> &filter) const {
+    std::vector<const App *> subcomms(subcommands_.size());
+    std::transform(
+        std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) { return v.get(); });
+
+    if(filter) {
+        subcomms.erase(std::remove_if(std::begin(subcomms),
+                                      std::end(subcomms),
+                                      [&filter](const App *app) { return !filter(app); }),
+                       std::end(subcomms));
+    }
+
+    return subcomms;
+}
+
+CLI11_INLINE std::vector<App *> App::get_subcommands(const std::function<bool(App *)> &filter) {
+    std::vector<App *> subcomms(subcommands_.size());
+    std::transform(
+        std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) { return v.get(); });
+
+    if(filter) {
+        subcomms.erase(
+            std::remove_if(std::begin(subcomms), std::end(subcomms), [&filter](App *app) { return !filter(app); }),
+            std::end(subcomms));
+    }
+
+    return subcomms;
+}
+
+CLI11_INLINE bool App::remove_excludes(Option *opt) {
+    auto iterator = std::find(std::begin(exclude_options_), std::end(exclude_options_), opt);
+    if(iterator == std::end(exclude_options_)) {
+        return false;
+    }
+    exclude_options_.erase(iterator);
+    return true;
+}
+
+CLI11_INLINE bool App::remove_excludes(App *app) {
+    auto iterator = std::find(std::begin(exclude_subcommands_), std::end(exclude_subcommands_), app);
+    if(iterator == std::end(exclude_subcommands_)) {
+        return false;
+    }
+    auto *other_app = *iterator;
+    exclude_subcommands_.erase(iterator);
+    other_app->remove_excludes(this);
+    return true;
+}
+
+CLI11_INLINE bool App::remove_needs(Option *opt) {
+    auto iterator = std::find(std::begin(need_options_), std::end(need_options_), opt);
+    if(iterator == std::end(need_options_)) {
+        return false;
+    }
+    need_options_.erase(iterator);
+    return true;
+}
+
+CLI11_INLINE bool App::remove_needs(App *app) {
+    auto iterator = std::find(std::begin(need_subcommands_), std::end(need_subcommands_), app);
+    if(iterator == std::end(need_subcommands_)) {
+        return false;
+    }
+    need_subcommands_.erase(iterator);
+    return true;
+}
+
+CLI11_NODISCARD CLI11_INLINE std::string App::help(std::string prev, AppFormatMode mode) const {
+    if(prev.empty())
+        prev = get_name();
+    else
+        prev += " " + get_name();
+
+    // Delegate to subcommand if needed
+    auto selected_subcommands = get_subcommands();
+    if(!selected_subcommands.empty()) {
+        return selected_subcommands.back()->help(prev, mode);
+    }
+    return formatter_->make_help(this, prev, mode);
+}
+
+CLI11_NODISCARD CLI11_INLINE std::string App::version() const {
+    std::string val;
+    if(version_ptr_ != nullptr) {
+        auto rv = version_ptr_->results();
+        version_ptr_->clear();
+        version_ptr_->add_result("true");
+        try {
+            version_ptr_->run_callback();
+        } catch(const CLI::CallForVersion &cfv) {
+            val = cfv.what();
+        }
+        version_ptr_->clear();
+        version_ptr_->add_result(rv);
+    }
+    return val;
+}
+
+CLI11_INLINE std::vector<const Option *> App::get_options(const std::function<bool(const Option *)> filter) const {
+    std::vector<const Option *> options(options_.size());
+    std::transform(
+        std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) { return val.get(); });
+
+    if(filter) {
+        options.erase(std::remove_if(std::begin(options),
+                                     std::end(options),
+                                     [&filter](const Option *opt) { return !filter(opt); }),
+                      std::end(options));
+    }
+
+    return options;
+}
+
+CLI11_INLINE std::vector<Option *> App::get_options(const std::function<bool(Option *)> filter) {
+    std::vector<Option *> options(options_.size());
+    std::transform(
+        std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) { return val.get(); });
+
+    if(filter) {
+        options.erase(
+            std::remove_if(std::begin(options), std::end(options), [&filter](Option *opt) { return !filter(opt); }),
+            std::end(options));
+    }
+
+    return options;
+}
+
+CLI11_INLINE Option *App::get_option_no_throw(std::string option_name) noexcept {
+    for(Option_p &opt : options_) {
+        if(opt->check_name(option_name)) {
+            return opt.get();
+        }
+    }
+    for(auto &subc : subcommands_) {
+        // also check down into nameless subcommands
+        if(subc->get_name().empty()) {
+            auto *opt = subc->get_option_no_throw(option_name);
+            if(opt != nullptr) {
+                return opt;
+            }
+        }
+    }
+    return nullptr;
+}
+
+CLI11_NODISCARD CLI11_INLINE const Option *App::get_option_no_throw(std::string option_name) const noexcept {
+    for(const Option_p &opt : options_) {
+        if(opt->check_name(option_name)) {
+            return opt.get();
+        }
+    }
+    for(const auto &subc : subcommands_) {
+        // also check down into nameless subcommands
+        if(subc->get_name().empty()) {
+            auto *opt = subc->get_option_no_throw(option_name);
+            if(opt != nullptr) {
+                return opt;
+            }
+        }
+    }
+    return nullptr;
+}
+
+CLI11_NODISCARD CLI11_INLINE std::string App::get_display_name(bool with_aliases) const {
+    if(name_.empty()) {
+        return std::string("[Option Group: ") + get_group() + "]";
+    }
+    if(aliases_.empty() || !with_aliases) {
+        return name_;
+    }
+    std::string dispname = name_;
+    for(const auto &lalias : aliases_) {
+        dispname.push_back(',');
+        dispname.push_back(' ');
+        dispname.append(lalias);
+    }
+    return dispname;
+}
+
+CLI11_NODISCARD CLI11_INLINE bool App::check_name(std::string name_to_check) const {
+    std::string local_name = name_;
+    if(ignore_underscore_) {
+        local_name = detail::remove_underscore(name_);
+        name_to_check = detail::remove_underscore(name_to_check);
+    }
+    if(ignore_case_) {
+        local_name = detail::to_lower(name_);
+        name_to_check = detail::to_lower(name_to_check);
+    }
+
+    if(local_name == name_to_check) {
+        return true;
+    }
+    for(auto les : aliases_) {  // NOLINT(performance-for-range-copy)
+        if(ignore_underscore_) {
+            les = detail::remove_underscore(les);
+        }
+        if(ignore_case_) {
+            les = detail::to_lower(les);
+        }
+        if(les == name_to_check) {
+            return true;
+        }
+    }
+    return false;
+}
+
+CLI11_NODISCARD CLI11_INLINE std::vector<std::string> App::get_groups() const {
+    std::vector<std::string> groups;
+
+    for(const Option_p &opt : options_) {
+        // Add group if it is not already in there
+        if(std::find(groups.begin(), groups.end(), opt->get_group()) == groups.end()) {
+            groups.push_back(opt->get_group());
+        }
+    }
+
+    return groups;
+}
+
+CLI11_NODISCARD CLI11_INLINE std::vector<std::string> App::remaining(bool recurse) const {
+    std::vector<std::string> miss_list;
+    for(const std::pair<detail::Classifier, std::string> &miss : missing_) {
+        miss_list.push_back(std::get<1>(miss));
+    }
+    // Get from a subcommand that may allow extras
+    if(recurse) {
+        if(!allow_extras_) {
+            for(const auto &sub : subcommands_) {
+                if(sub->name_.empty() && !sub->missing_.empty()) {
+                    for(const std::pair<detail::Classifier, std::string> &miss : sub->missing_) {
+                        miss_list.push_back(std::get<1>(miss));
+                    }
+                }
+            }
+        }
+        // Recurse into subcommands
+
+        for(const App *sub : parsed_subcommands_) {
+            std::vector<std::string> output = sub->remaining(recurse);
+            std::copy(std::begin(output), std::end(output), std::back_inserter(miss_list));
+        }
+    }
+    return miss_list;
+}
+
+CLI11_NODISCARD CLI11_INLINE std::vector<std::string> App::remaining_for_passthrough(bool recurse) const {
+    std::vector<std::string> miss_list = remaining(recurse);
+    std::reverse(std::begin(miss_list), std::end(miss_list));
+    return miss_list;
+}
+
+CLI11_NODISCARD CLI11_INLINE std::size_t App::remaining_size(bool recurse) const {
+    auto remaining_options = static_cast<std::size_t>(std::count_if(
+        std::begin(missing_), std::end(missing_), [](const std::pair<detail::Classifier, std::string> &val) {
+            return val.first != detail::Classifier::POSITIONAL_MARK;
+        }));
+
+    if(recurse) {
+        for(const App_p &sub : subcommands_) {
+            remaining_options += sub->remaining_size(recurse);
+        }
+    }
+    return remaining_options;
+}
+
+CLI11_INLINE void App::_validate() const {
+    // count the number of positional only args
+    auto pcount = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) {
+        return opt->get_items_expected_max() >= detail::expected_max_vector_size && !opt->nonpositional();
+    });
+    if(pcount > 1) {
+        auto pcount_req = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) {
+            return opt->get_items_expected_max() >= detail::expected_max_vector_size && !opt->nonpositional() &&
+                   opt->get_required();
+        });
+        if(pcount - pcount_req > 1) {
+            throw InvalidError(name_);
+        }
+    }
+
+    std::size_t nameless_subs{0};
+    for(const App_p &app : subcommands_) {
+        app->_validate();
+        if(app->get_name().empty())
+            ++nameless_subs;
+    }
+
+    if(require_option_min_ > 0) {
+        if(require_option_max_ > 0) {
+            if(require_option_max_ < require_option_min_) {
+                throw(InvalidError("Required min options greater than required max options", ExitCodes::InvalidError));
+            }
+        }
+        if(require_option_min_ > (options_.size() + nameless_subs)) {
+            throw(
+                InvalidError("Required min options greater than number of available options", ExitCodes::InvalidError));
+        }
+    }
+}
+
+CLI11_INLINE void App::_configure() {
+    if(default_startup == startup_mode::enabled) {
+        disabled_ = false;
+    } else if(default_startup == startup_mode::disabled) {
+        disabled_ = true;
+    }
+    for(const App_p &app : subcommands_) {
+        if(app->has_automatic_name_) {
+            app->name_.clear();
+        }
+        if(app->name_.empty()) {
+            app->fallthrough_ = false;  // make sure fallthrough_ is false to prevent infinite loop
+            app->prefix_command_ = false;
+        }
+        // make sure the parent is set to be this object in preparation for parse
+        app->parent_ = this;
+        app->_configure();
+    }
+}
+
+CLI11_INLINE void App::run_callback(bool final_mode, bool suppress_final_callback) {
+    pre_callback();
+    // in the main app if immediate_callback_ is set it runs the main callback before the used subcommands
+    if(!final_mode && parse_complete_callback_) {
+        parse_complete_callback_();
+    }
+    // run the callbacks for the received subcommands
+    for(App *subc : get_subcommands()) {
+        if(subc->parent_ == this) {
+            subc->run_callback(true, suppress_final_callback);
+        }
+    }
+    // now run callbacks for option_groups
+    for(auto &subc : subcommands_) {
+        if(subc->name_.empty() && subc->count_all() > 0) {
+            subc->run_callback(true, suppress_final_callback);
+        }
+    }
+
+    // finally run the main callback
+    if(final_callback_ && (parsed_ > 0) && (!suppress_final_callback)) {
+        if(!name_.empty() || count_all() > 0 || parent_ == nullptr) {
+            final_callback_();
+        }
+    }
+}
+
+CLI11_NODISCARD CLI11_INLINE bool App::_valid_subcommand(const std::string &current, bool ignore_used) const {
+    // Don't match if max has been reached - but still check parents
+    if(require_subcommand_max_ != 0 && parsed_subcommands_.size() >= require_subcommand_max_) {
+        return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used);
+    }
+    auto *com = _find_subcommand(current, true, ignore_used);
+    if(com != nullptr) {
+        return true;
+    }
+    // Check parent if exists, else return false
+    return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used);
+}
+
+CLI11_NODISCARD CLI11_INLINE detail::Classifier App::_recognize(const std::string &current,
+                                                                bool ignore_used_subcommands) const {
+    std::string dummy1, dummy2;
+
+    if(current == "--")
+        return detail::Classifier::POSITIONAL_MARK;
+    if(_valid_subcommand(current, ignore_used_subcommands))
+        return detail::Classifier::SUBCOMMAND;
+    if(detail::split_long(current, dummy1, dummy2))
+        return detail::Classifier::LONG;
+    if(detail::split_short(current, dummy1, dummy2)) {
+        if(dummy1[0] >= '0' && dummy1[0] <= '9') {
+            if(get_option_no_throw(std::string{'-', dummy1[0]}) == nullptr) {
+                return detail::Classifier::NONE;
+            }
+        }
+        return detail::Classifier::SHORT;
+    }
+    if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2)))
+        return detail::Classifier::WINDOWS_STYLE;
+    if((current == "++") && !name_.empty() && parent_ != nullptr)
+        return detail::Classifier::SUBCOMMAND_TERMINATOR;
+    return detail::Classifier::NONE;
+}
+
+CLI11_INLINE void App::_process_config_file() {
+    if(config_ptr_ != nullptr) {
+        bool config_required = config_ptr_->get_required();
+        auto file_given = config_ptr_->count() > 0;
+        auto config_files = config_ptr_->as<std::vector<std::string>>();
+        if(config_files.empty() || config_files.front().empty()) {
+            if(config_required) {
+                throw FileError::Missing("no specified config file");
+            }
+            return;
+        }
+        for(auto rit = config_files.rbegin(); rit != config_files.rend(); ++rit) {
+            const auto &config_file = *rit;
+            auto path_result = detail::check_path(config_file.c_str());
+            if(path_result == detail::path_type::file) {
+                try {
+                    std::vector<ConfigItem> values = config_formatter_->from_file(config_file);
+                    _parse_config(values);
+                    if(!file_given) {
+                        config_ptr_->add_result(config_file);
+                    }
+                } catch(const FileError &) {
+                    if(config_required || file_given)
+                        throw;
+                }
+            } else if(config_required || file_given) {
+                throw FileError::Missing(config_file);
+            }
+        }
+    }
+}
+
+CLI11_INLINE void App::_process_env() {
+    for(const Option_p &opt : options_) {
+        if(opt->count() == 0 && !opt->envname_.empty()) {
+            char *buffer = nullptr;
+            std::string ename_string;
+
+#ifdef _MSC_VER
+            // Windows version
+            std::size_t sz = 0;
+            if(_dupenv_s(&buffer, &sz, opt->envname_.c_str()) == 0 && buffer != nullptr) {
+                ename_string = std::string(buffer);
+                free(buffer);
+            }
+#else
+            // This also works on Windows, but gives a warning
+            buffer = std::getenv(opt->envname_.c_str());
+            if(buffer != nullptr)
+                ename_string = std::string(buffer);
+#endif
+
+            if(!ename_string.empty()) {
+                opt->add_result(ename_string);
+            }
+        }
+    }
+
+    for(App_p &sub : subcommands_) {
+        if(sub->get_name().empty() || !sub->parse_complete_callback_)
+            sub->_process_env();
+    }
+}
+
+CLI11_INLINE void App::_process_callbacks() {
+
+    for(App_p &sub : subcommands_) {
+        // process the priority option_groups first
+        if(sub->get_name().empty() && sub->parse_complete_callback_) {
+            if(sub->count_all() > 0) {
+                sub->_process_callbacks();
+                sub->run_callback();
+            }
+        }
+    }
+
+    for(const Option_p &opt : options_) {
+        if((*opt) && !opt->get_callback_run()) {
+            opt->run_callback();
+        }
+    }
+    for(App_p &sub : subcommands_) {
+        if(!sub->parse_complete_callback_) {
+            sub->_process_callbacks();
+        }
+    }
+}
+
+CLI11_INLINE void App::_process_help_flags(bool trigger_help, bool trigger_all_help) const {
+    const Option *help_ptr = get_help_ptr();
+    const Option *help_all_ptr = get_help_all_ptr();
+
+    if(help_ptr != nullptr && help_ptr->count() > 0)
+        trigger_help = true;
+    if(help_all_ptr != nullptr && help_all_ptr->count() > 0)
+        trigger_all_help = true;
+
+    // If there were parsed subcommands, call those. First subcommand wins if there are multiple ones.
+    if(!parsed_subcommands_.empty()) {
+        for(const App *sub : parsed_subcommands_)
+            sub->_process_help_flags(trigger_help, trigger_all_help);
+
+        // Only the final subcommand should call for help. All help wins over help.
+    } else if(trigger_all_help) {
+        throw CallForAllHelp();
+    } else if(trigger_help) {
+        throw CallForHelp();
+    }
+}
+
+CLI11_INLINE void App::_process_requirements() {
+    // check excludes
+    bool excluded{false};
+    std::string excluder;
+    for(const auto &opt : exclude_options_) {
+        if(opt->count() > 0) {
+            excluded = true;
+            excluder = opt->get_name();
+        }
+    }
+    for(const auto &subc : exclude_subcommands_) {
+        if(subc->count_all() > 0) {
+            excluded = true;
+            excluder = subc->get_display_name();
+        }
+    }
+    if(excluded) {
+        if(count_all() > 0) {
+            throw ExcludesError(get_display_name(), excluder);
+        }
+        // if we are excluded but didn't receive anything, just return
+        return;
+    }
+
+    // check excludes
+    bool missing_needed{false};
+    std::string missing_need;
+    for(const auto &opt : need_options_) {
+        if(opt->count() == 0) {
+            missing_needed = true;
+            missing_need = opt->get_name();
+        }
+    }
+    for(const auto &subc : need_subcommands_) {
+        if(subc->count_all() == 0) {
+            missing_needed = true;
+            missing_need = subc->get_display_name();
+        }
+    }
+    if(missing_needed) {
+        if(count_all() > 0) {
+            throw RequiresError(get_display_name(), missing_need);
+        }
+        // if we missing something but didn't have any options, just return
+        return;
+    }
+
+    std::size_t used_options = 0;
+    for(const Option_p &opt : options_) {
+
+        if(opt->count() != 0) {
+            ++used_options;
+        }
+        // Required but empty
+        if(opt->get_required() && opt->count() == 0) {
+            throw RequiredError(opt->get_name());
+        }
+        // Requires
+        for(const Option *opt_req : opt->needs_)
+            if(opt->count() > 0 && opt_req->count() == 0)
+                throw RequiresError(opt->get_name(), opt_req->get_name());
+        // Excludes
+        for(const Option *opt_ex : opt->excludes_)
+            if(opt->count() > 0 && opt_ex->count() != 0)
+                throw ExcludesError(opt->get_name(), opt_ex->get_name());
+    }
+    // check for the required number of subcommands
+    if(require_subcommand_min_ > 0) {
+        auto selected_subcommands = get_subcommands();
+        if(require_subcommand_min_ > selected_subcommands.size())
+            throw RequiredError::Subcommand(require_subcommand_min_);
+    }
+
+    // Max error cannot occur, the extra subcommand will parse as an ExtrasError or a remaining item.
+
+    // run this loop to check how many unnamed subcommands were actually used since they are considered options
+    // from the perspective of an App
+    for(App_p &sub : subcommands_) {
+        if(sub->disabled_)
+            continue;
+        if(sub->name_.empty() && sub->count_all() > 0) {
+            ++used_options;
+        }
+    }
+
+    if(require_option_min_ > used_options || (require_option_max_ > 0 && require_option_max_ < used_options)) {
+        auto option_list = detail::join(options_, [this](const Option_p &ptr) {
+            if(ptr.get() == help_ptr_ || ptr.get() == help_all_ptr_) {
+                return std::string{};
+            }
+            return ptr->get_name(false, true);
+        });
+
+        auto subc_list = get_subcommands([](App *app) { return ((app->get_name().empty()) && (!app->disabled_)); });
+        if(!subc_list.empty()) {
+            option_list += "," + detail::join(subc_list, [](const App *app) { return app->get_display_name(); });
+        }
+        throw RequiredError::Option(require_option_min_, require_option_max_, used_options, option_list);
+    }
+
+    // now process the requirements for subcommands if needed
+    for(App_p &sub : subcommands_) {
+        if(sub->disabled_)
+            continue;
+        if(sub->name_.empty() && sub->required_ == false) {
+            if(sub->count_all() == 0) {
+                if(require_option_min_ > 0 && require_option_min_ <= used_options) {
+                    continue;
+                    // if we have met the requirement and there is nothing in this option group skip checking
+                    // requirements
+                }
+                if(require_option_max_ > 0 && used_options >= require_option_min_) {
+                    continue;
+                    // if we have met the requirement and there is nothing in this option group skip checking
+                    // requirements
+                }
+            }
+        }
+        if(sub->count() > 0 || sub->name_.empty()) {
+            sub->_process_requirements();
+        }
+
+        if(sub->required_ && sub->count_all() == 0) {
+            throw(CLI::RequiredError(sub->get_display_name()));
+        }
+    }
+}
+
+CLI11_INLINE void App::_process() {
+    try {
+        // the config file might generate a FileError but that should not be processed until later in the process
+        // to allow for help, version and other errors to generate first.
+        _process_config_file();
+
+        // process env shouldn't throw but no reason to process it if config generated an error
+        _process_env();
+    } catch(const CLI::FileError &) {
+        // callbacks and help_flags can generate exceptions which should take priority
+        // over the config file error if one exists.
+        _process_callbacks();
+        _process_help_flags();
+        throw;
+    }
+
+    _process_callbacks();
+    _process_help_flags();
+
+    _process_requirements();
+}
+
+CLI11_INLINE void App::_process_extras() {
+    if(!(allow_extras_ || prefix_command_)) {
+        std::size_t num_left_over = remaining_size();
+        if(num_left_over > 0) {
+            throw ExtrasError(name_, remaining(false));
+        }
+    }
+
+    for(App_p &sub : subcommands_) {
+        if(sub->count() > 0)
+            sub->_process_extras();
+    }
+}
+
+CLI11_INLINE void App::_process_extras(std::vector<std::string> &args) {
+    if(!(allow_extras_ || prefix_command_)) {
+        std::size_t num_left_over = remaining_size();
+        if(num_left_over > 0) {
+            args = remaining(false);
+            throw ExtrasError(name_, args);
+        }
+    }
+
+    for(App_p &sub : subcommands_) {
+        if(sub->count() > 0)
+            sub->_process_extras(args);
+    }
+}
+
+CLI11_INLINE void App::increment_parsed() {
+    ++parsed_;
+    for(App_p &sub : subcommands_) {
+        if(sub->get_name().empty())
+            sub->increment_parsed();
+    }
+}
+
+CLI11_INLINE void App::_parse(std::vector<std::string> &args) {
+    increment_parsed();
+    _trigger_pre_parse(args.size());
+    bool positional_only = false;
+
+    while(!args.empty()) {
+        if(!_parse_single(args, positional_only)) {
+            break;
+        }
+    }
+
+    if(parent_ == nullptr) {
+        _process();
+
+        // Throw error if any items are left over (depending on settings)
+        _process_extras(args);
+
+        // Convert missing (pairs) to extras (string only) ready for processing in another app
+        args = remaining_for_passthrough(false);
+    } else if(parse_complete_callback_) {
+        _process_env();
+        _process_callbacks();
+        _process_help_flags();
+        _process_requirements();
+        run_callback(false, true);
+    }
+}
+
+CLI11_INLINE void App::_parse(std::vector<std::string> &&args) {
+    // this can only be called by the top level in which case parent == nullptr by definition
+    // operation is simplified
+    increment_parsed();
+    _trigger_pre_parse(args.size());
+    bool positional_only = false;
+
+    while(!args.empty()) {
+        _parse_single(args, positional_only);
+    }
+    _process();
+
+    // Throw error if any items are left over (depending on settings)
+    _process_extras();
+}
+
+CLI11_INLINE void App::_parse_stream(std::istream &input) {
+    auto values = config_formatter_->from_config(input);
+    _parse_config(values);
+    increment_parsed();
+    _trigger_pre_parse(values.size());
+    _process();
+
+    // Throw error if any items are left over (depending on settings)
+    _process_extras();
+}
+
+CLI11_INLINE void App::_parse_config(const std::vector<ConfigItem> &args) {
+    for(const ConfigItem &item : args) {
+        if(!_parse_single_config(item) && allow_config_extras_ == config_extras_mode::error)
+            throw ConfigError::Extras(item.fullname());
+    }
+}
+
+CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t level) {
+    if(level < item.parents.size()) {
+        try {
+            auto *subcom = get_subcommand(item.parents.at(level));
+            auto result = subcom->_parse_single_config(item, level + 1);
+
+            return result;
+        } catch(const OptionNotFound &) {
+            return false;
+        }
+    }
+    // check for section open
+    if(item.name == "++") {
+        if(configurable_) {
+            increment_parsed();
+            _trigger_pre_parse(2);
+            if(parent_ != nullptr) {
+                parent_->parsed_subcommands_.push_back(this);
+            }
+        }
+        return true;
+    }
+    // check for section close
+    if(item.name == "--") {
+        if(configurable_ && parse_complete_callback_) {
+            _process_callbacks();
+            _process_requirements();
+            run_callback();
+        }
+        return true;
+    }
+    Option *op = get_option_no_throw("--" + item.name);
+    if(op == nullptr) {
+        if(item.name.size() == 1) {
+            op = get_option_no_throw("-" + item.name);
+        }
+    }
+    if(op == nullptr) {
+        op = get_option_no_throw(item.name);
+    }
+    if(op == nullptr) {
+        // If the option was not present
+        if(get_allow_config_extras() == config_extras_mode::capture)
+            // Should we worry about classifying the extras properly?
+            missing_.emplace_back(detail::Classifier::NONE, item.fullname());
+        return false;
+    }
+
+    if(!op->get_configurable()) {
+        if(get_allow_config_extras() == config_extras_mode::ignore_all) {
+            return false;
+        }
+        throw ConfigError::NotConfigurable(item.fullname());
+    }
+
+    if(op->empty()) {
+
+        if(op->get_expected_min() == 0) {
+            if(item.inputs.size() <= 1) {
+                // Flag parsing
+                auto res = config_formatter_->to_flag(item);
+                bool converted{false};
+                if(op->get_disable_flag_override()) {
+
+                    try {
+                        auto val = detail::to_flag_value(res);
+                        if(val == 1) {
+                            res = op->get_flag_value(item.name, "{}");
+                            converted = true;
+                        }
+                    } catch(...) {
+                    }
+                }
+
+                if(!converted) {
+                    res = op->get_flag_value(item.name, res);
+                }
+
+                op->add_result(res);
+                return true;
+            }
+            if(static_cast<int>(item.inputs.size()) > op->get_items_expected_max()) {
+                if(op->get_items_expected_max() > 1) {
+                    throw ArgumentMismatch::AtMost(item.fullname(), op->get_items_expected_max(), item.inputs.size());
+                }
+                throw ConversionError::TooManyInputsFlag(item.fullname());
+            }
+        }
+        op->add_result(item.inputs);
+        op->run_callback();
+    }
+
+    return true;
+}
+
+CLI11_INLINE bool App::_parse_single(std::vector<std::string> &args, bool &positional_only) {
+    bool retval = true;
+    detail::Classifier classifier = positional_only ? detail::Classifier::NONE : _recognize(args.back());
+    switch(classifier) {
+    case detail::Classifier::POSITIONAL_MARK:
+        args.pop_back();
+        positional_only = true;
+        if((!_has_remaining_positionals()) && (parent_ != nullptr)) {
+            retval = false;
+        } else {
+            _move_to_missing(classifier, "--");
+        }
+        break;
+    case detail::Classifier::SUBCOMMAND_TERMINATOR:
+        // treat this like a positional mark if in the parent app
+        args.pop_back();
+        retval = false;
+        break;
+    case detail::Classifier::SUBCOMMAND:
+        retval = _parse_subcommand(args);
+        break;
+    case detail::Classifier::LONG:
+    case detail::Classifier::SHORT:
+    case detail::Classifier::WINDOWS_STYLE:
+        // If already parsed a subcommand, don't accept options_
+        _parse_arg(args, classifier);
+        break;
+    case detail::Classifier::NONE:
+        // Probably a positional or something for a parent (sub)command
+        retval = _parse_positional(args, false);
+        if(retval && positionals_at_end_) {
+            positional_only = true;
+        }
+        break;
+        // LCOV_EXCL_START
+    default:
+        throw HorribleError("unrecognized classifier (you should not see this!)");
+        // LCOV_EXCL_STOP
+    }
+    return retval;
+}
+
+CLI11_NODISCARD CLI11_INLINE std::size_t App::_count_remaining_positionals(bool required_only) const {
+    std::size_t retval = 0;
+    for(const Option_p &opt : options_) {
+        if(opt->get_positional() && (!required_only || opt->get_required())) {
+            if(opt->get_items_expected_min() > 0 && static_cast<int>(opt->count()) < opt->get_items_expected_min()) {
+                retval += static_cast<std::size_t>(opt->get_items_expected_min()) - opt->count();
+            }
+        }
+    }
+    return retval;
+}
+
+CLI11_NODISCARD CLI11_INLINE bool App::_has_remaining_positionals() const {
+    for(const Option_p &opt : options_) {
+        if(opt->get_positional() && ((static_cast<int>(opt->count()) < opt->get_items_expected_min()))) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+CLI11_INLINE bool App::_parse_positional(std::vector<std::string> &args, bool haltOnSubcommand) {
+
+    const std::string &positional = args.back();
+
+    if(positionals_at_end_) {
+        // deal with the case of required arguments at the end which should take precedence over other arguments
+        auto arg_rem = args.size();
+        auto remreq = _count_remaining_positionals(true);
+        if(arg_rem <= remreq) {
+            for(const Option_p &opt : options_) {
+                if(opt->get_positional() && opt->required_) {
+                    if(static_cast<int>(opt->count()) < opt->get_items_expected_min()) {
+                        if(validate_positionals_) {
+                            std::string pos = positional;
+                            pos = opt->_validate(pos, 0);
+                            if(!pos.empty()) {
+                                continue;
+                            }
+                        }
+
+                        parse_order_.push_back(opt.get());
+                        /// if we require a separator add it here
+                        if(opt->get_inject_separator()) {
+                            if(!opt->results().empty() && !opt->results().back().empty()) {
+                                opt->add_result(std::string{});
+                            }
+                        }
+                        if(opt->get_trigger_on_parse() &&
+                           opt->current_option_state_ == Option::option_state::callback_run) {
+                            opt->clear();
+                        }
+                        opt->add_result(positional);
+                        if(opt->get_trigger_on_parse()) {
+                            opt->run_callback();
+                        }
+                        args.pop_back();
+                        return true;
+                    }
+                }
+            }
+        }
+    }
+    for(const Option_p &opt : options_) {
+        // Eat options, one by one, until done
+        if(opt->get_positional() &&
+           (static_cast<int>(opt->count()) < opt->get_items_expected_min() || opt->get_allow_extra_args())) {
+            if(validate_positionals_) {
+                std::string pos = positional;
+                pos = opt->_validate(pos, 0);
+                if(!pos.empty()) {
+                    continue;
+                }
+            }
+            if(opt->get_inject_separator()) {
+                if(!opt->results().empty() && !opt->results().back().empty()) {
+                    opt->add_result(std::string{});
+                }
+            }
+            if(opt->get_trigger_on_parse() && opt->current_option_state_ == Option::option_state::callback_run) {
+                opt->clear();
+            }
+            opt->add_result(positional);
+            if(opt->get_trigger_on_parse()) {
+                opt->run_callback();
+            }
+            parse_order_.push_back(opt.get());
+            args.pop_back();
+            return true;
+        }
+    }
+
+    for(auto &subc : subcommands_) {
+        if((subc->name_.empty()) && (!subc->disabled_)) {
+            if(subc->_parse_positional(args, false)) {
+                if(!subc->pre_parse_called_) {
+                    subc->_trigger_pre_parse(args.size());
+                }
+                return true;
+            }
+        }
+    }
+    // let the parent deal with it if possible
+    if(parent_ != nullptr && fallthrough_)
+        return _get_fallthrough_parent()->_parse_positional(args, static_cast<bool>(parse_complete_callback_));
+
+    /// Try to find a local subcommand that is repeated
+    auto *com = _find_subcommand(args.back(), true, false);
+    if(com != nullptr && (require_subcommand_max_ == 0 || require_subcommand_max_ > parsed_subcommands_.size())) {
+        if(haltOnSubcommand) {
+            return false;
+        }
+        args.pop_back();
+        com->_parse(args);
+        return true;
+    }
+    /// now try one last gasp at subcommands that have been executed before, go to root app and try to find a
+    /// subcommand in a broader way, if one exists let the parent deal with it
+    auto *parent_app = (parent_ != nullptr) ? _get_fallthrough_parent() : this;
+    com = parent_app->_find_subcommand(args.back(), true, false);
+    if(com != nullptr && (com->parent_->require_subcommand_max_ == 0 ||
+                          com->parent_->require_subcommand_max_ > com->parent_->parsed_subcommands_.size())) {
+        return false;
+    }
+
+    if(positionals_at_end_) {
+        throw CLI::ExtrasError(name_, args);
+    }
+    /// If this is an option group don't deal with it
+    if(parent_ != nullptr && name_.empty()) {
+        return false;
+    }
+    /// We are out of other options this goes to missing
+    _move_to_missing(detail::Classifier::NONE, positional);
+    args.pop_back();
+    if(prefix_command_) {
+        while(!args.empty()) {
+            _move_to_missing(detail::Classifier::NONE, args.back());
+            args.pop_back();
+        }
+    }
+
+    return true;
+}
+
+CLI11_NODISCARD CLI11_INLINE App *
+App::_find_subcommand(const std::string &subc_name, bool ignore_disabled, bool ignore_used) const noexcept {
+    for(const App_p &com : subcommands_) {
+        if(com->disabled_ && ignore_disabled)
+            continue;
+        if(com->get_name().empty()) {
+            auto *subc = com->_find_subcommand(subc_name, ignore_disabled, ignore_used);
+            if(subc != nullptr) {
+                return subc;
+            }
+        }
+        if(com->check_name(subc_name)) {
+            if((!*com) || !ignore_used)
+                return com.get();
+        }
+    }
+    return nullptr;
+}
+
+CLI11_INLINE bool App::_parse_subcommand(std::vector<std::string> &args) {
+    if(_count_remaining_positionals(/* required */ true) > 0) {
+        _parse_positional(args, false);
+        return true;
+    }
+    auto *com = _find_subcommand(args.back(), true, true);
+    if(com != nullptr) {
+        args.pop_back();
+        if(!com->silent_) {
+            parsed_subcommands_.push_back(com);
+        }
+        com->_parse(args);
+        auto *parent_app = com->parent_;
+        while(parent_app != this) {
+            parent_app->_trigger_pre_parse(args.size());
+            if(!com->silent_) {
+                parent_app->parsed_subcommands_.push_back(com);
+            }
+            parent_app = parent_app->parent_;
+        }
+        return true;
+    }
+
+    if(parent_ == nullptr)
+        throw HorribleError("Subcommand " + args.back() + " missing");
+    return false;
+}
+
+CLI11_INLINE bool App::_parse_arg(std::vector<std::string> &args, detail::Classifier current_type) {
+
+    std::string current = args.back();
+
+    std::string arg_name;
+    std::string value;
+    std::string rest;
+
+    switch(current_type) {
+    case detail::Classifier::LONG:
+        if(!detail::split_long(current, arg_name, value))
+            throw HorribleError("Long parsed but missing (you should not see this):" + args.back());
+        break;
+    case detail::Classifier::SHORT:
+        if(!detail::split_short(current, arg_name, rest))
+            throw HorribleError("Short parsed but missing! You should not see this");
+        break;
+    case detail::Classifier::WINDOWS_STYLE:
+        if(!detail::split_windows_style(current, arg_name, value))
+            throw HorribleError("windows option parsed but missing! You should not see this");
+        break;
+    case detail::Classifier::SUBCOMMAND:
+    case detail::Classifier::SUBCOMMAND_TERMINATOR:
+    case detail::Classifier::POSITIONAL_MARK:
+    case detail::Classifier::NONE:
+    default:
+        throw HorribleError("parsing got called with invalid option! You should not see this");
+    }
+
+    auto op_ptr = std::find_if(std::begin(options_), std::end(options_), [arg_name, current_type](const Option_p &opt) {
+        if(current_type == detail::Classifier::LONG)
+            return opt->check_lname(arg_name);
+        if(current_type == detail::Classifier::SHORT)
+            return opt->check_sname(arg_name);
+        // this will only get called for detail::Classifier::WINDOWS_STYLE
+        return opt->check_lname(arg_name) || opt->check_sname(arg_name);
+    });
+
+    // Option not found
+    if(op_ptr == std::end(options_)) {
+        for(auto &subc : subcommands_) {
+            if(subc->name_.empty() && !subc->disabled_) {
+                if(subc->_parse_arg(args, current_type)) {
+                    if(!subc->pre_parse_called_) {
+                        subc->_trigger_pre_parse(args.size());
+                    }
+                    return true;
+                }
+            }
+        }
+
+        // don't capture missing if this is a nameless subcommand and nameless subcommands can't fallthrough
+        if(parent_ != nullptr && name_.empty()) {
+            return false;
+        }
+
+        // If a subcommand, try the main command
+        if(parent_ != nullptr && fallthrough_)
+            return _get_fallthrough_parent()->_parse_arg(args, current_type);
+
+        // Otherwise, add to missing
+        args.pop_back();
+        _move_to_missing(current_type, current);
+        return true;
+    }
+
+    args.pop_back();
+
+    // Get a reference to the pointer to make syntax bearable
+    Option_p &op = *op_ptr;
+    /// if we require a separator add it here
+    if(op->get_inject_separator()) {
+        if(!op->results().empty() && !op->results().back().empty()) {
+            op->add_result(std::string{});
+        }
+    }
+    if(op->get_trigger_on_parse() && op->current_option_state_ == Option::option_state::callback_run) {
+        op->clear();
+    }
+    int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min());
+    int max_num = op->get_items_expected_max();
+    // check container like options to limit the argument size to a single type if the allow_extra_flags argument is
+    // set. 16 is somewhat arbitrary (needs to be at least 4)
+    if(max_num >= detail::expected_max_vector_size / 16 && !op->get_allow_extra_args()) {
+        auto tmax = op->get_type_size_max();
+        max_num = detail::checked_multiply(tmax, op->get_expected_min()) ? tmax : detail::expected_max_vector_size;
+    }
+    // Make sure we always eat the minimum for unlimited vectors
+    int collected = 0;     // total number of arguments collected
+    int result_count = 0;  // local variable for number of results in a single arg string
+    // deal with purely flag like things
+    if(max_num == 0) {
+        auto res = op->get_flag_value(arg_name, value);
+        op->add_result(res);
+        parse_order_.push_back(op.get());
+    } else if(!value.empty()) {  // --this=value
+        op->add_result(value, result_count);
+        parse_order_.push_back(op.get());
+        collected += result_count;
+        // -Trest
+    } else if(!rest.empty()) {
+        op->add_result(rest, result_count);
+        parse_order_.push_back(op.get());
+        rest = "";
+        collected += result_count;
+    }
+
+    // gather the minimum number of arguments
+    while(min_num > collected && !args.empty()) {
+        std::string current_ = args.back();
+        args.pop_back();
+        op->add_result(current_, result_count);
+        parse_order_.push_back(op.get());
+        collected += result_count;
+    }
+
+    if(min_num > collected) {  // if we have run out of arguments and the minimum was not met
+        throw ArgumentMismatch::TypedAtLeast(op->get_name(), min_num, op->get_type_name());
+    }
+
+    // now check for optional arguments
+    if(max_num > collected || op->get_allow_extra_args()) {  // we allow optional arguments
+        auto remreqpos = _count_remaining_positionals(true);
+        // we have met the minimum now optionally check up to the maximum
+        while((collected < max_num || op->get_allow_extra_args()) && !args.empty() &&
+              _recognize(args.back(), false) == detail::Classifier::NONE) {
+            // If any required positionals remain, don't keep eating
+            if(remreqpos >= args.size()) {
+                break;
+            }
+            if(validate_optional_arguments_) {
+                std::string arg = args.back();
+                arg = op->_validate(arg, 0);
+                if(!arg.empty()) {
+                    break;
+                }
+            }
+            op->add_result(args.back(), result_count);
+            parse_order_.push_back(op.get());
+            args.pop_back();
+            collected += result_count;
+        }
+
+        // Allow -- to end an unlimited list and "eat" it
+        if(!args.empty() && _recognize(args.back()) == detail::Classifier::POSITIONAL_MARK)
+            args.pop_back();
+        // optional flag that didn't receive anything now get the default value
+        if(min_num == 0 && max_num > 0 && collected == 0) {
+            auto res = op->get_flag_value(arg_name, std::string{});
+            op->add_result(res);
+            parse_order_.push_back(op.get());
+        }
+    }
+    // if we only partially completed a type then add an empty string if allowed for later processing
+    if(min_num > 0 && (collected % op->get_type_size_max()) != 0) {
+        if(op->get_type_size_max() != op->get_type_size_min()) {
+            op->add_result(std::string{});
+        } else {
+            throw ArgumentMismatch::PartialType(op->get_name(), op->get_type_size_min(), op->get_type_name());
+        }
+    }
+    if(op->get_trigger_on_parse()) {
+        op->run_callback();
+    }
+    if(!rest.empty()) {
+        rest = "-" + rest;
+        args.push_back(rest);
+    }
+    return true;
+}
+
+CLI11_INLINE void App::_trigger_pre_parse(std::size_t remaining_args) {
+    if(!pre_parse_called_) {
+        pre_parse_called_ = true;
+        if(pre_parse_callback_) {
+            pre_parse_callback_(remaining_args);
+        }
+    } else if(immediate_callback_) {
+        if(!name_.empty()) {
+            auto pcnt = parsed_;
+            auto extras = std::move(missing_);
+            clear();
+            parsed_ = pcnt;
+            pre_parse_called_ = true;
+            missing_ = std::move(extras);
+        }
+    }
+}
+
+CLI11_INLINE App *App::_get_fallthrough_parent() {
+    if(parent_ == nullptr) {
+        throw(HorribleError("No Valid parent"));
+    }
+    auto *fallthrough_parent = parent_;
+    while((fallthrough_parent->parent_ != nullptr) && (fallthrough_parent->get_name().empty())) {
+        fallthrough_parent = fallthrough_parent->parent_;
+    }
+    return fallthrough_parent;
+}
+
+CLI11_NODISCARD CLI11_INLINE const std::string &App::_compare_subcommand_names(const App &subcom,
+                                                                               const App &base) const {
+    static const std::string estring;
+    if(subcom.disabled_) {
+        return estring;
+    }
+    for(const auto &subc : base.subcommands_) {
+        if(subc.get() != &subcom) {
+            if(subc->disabled_) {
+                continue;
+            }
+            if(!subcom.get_name().empty()) {
+                if(subc->check_name(subcom.get_name())) {
+                    return subcom.get_name();
+                }
+            }
+            if(!subc->get_name().empty()) {
+                if(subcom.check_name(subc->get_name())) {
+                    return subc->get_name();
+                }
+            }
+            for(const auto &les : subcom.aliases_) {
+                if(subc->check_name(les)) {
+                    return les;
+                }
+            }
+            // this loop is needed in case of ignore_underscore or ignore_case on one but not the other
+            for(const auto &les : subc->aliases_) {
+                if(subcom.check_name(les)) {
+                    return les;
+                }
+            }
+            // if the subcommand is an option group we need to check deeper
+            if(subc->get_name().empty()) {
+                const auto &cmpres = _compare_subcommand_names(subcom, *subc);
+                if(!cmpres.empty()) {
+                    return cmpres;
+                }
+            }
+            // if the test subcommand is an option group we need to check deeper
+            if(subcom.get_name().empty()) {
+                const auto &cmpres = _compare_subcommand_names(*subc, subcom);
+                if(!cmpres.empty()) {
+                    return cmpres;
+                }
+            }
+        }
+    }
+    return estring;
+}
+
+CLI11_INLINE void App::_move_to_missing(detail::Classifier val_type, const std::string &val) {
+    if(allow_extras_ || subcommands_.empty()) {
+        missing_.emplace_back(val_type, val);
+        return;
+    }
+    // allow extra arguments to be places in an option group if it is allowed there
+    for(auto &subc : subcommands_) {
+        if(subc->name_.empty() && subc->allow_extras_) {
+            subc->missing_.emplace_back(val_type, val);
+            return;
+        }
+    }
+    // if we haven't found any place to put them yet put them in missing
+    missing_.emplace_back(val_type, val);
+}
+
+CLI11_INLINE void App::_move_option(Option *opt, App *app) {
+    if(opt == nullptr) {
+        throw OptionNotFound("the option is NULL");
+    }
+    // verify that the give app is actually a subcommand
+    bool found = false;
+    for(auto &subc : subcommands_) {
+        if(app == subc.get()) {
+            found = true;
+        }
+    }
+    if(!found) {
+        throw OptionNotFound("The Given app is not a subcommand");
+    }
+
+    if((help_ptr_ == opt) || (help_all_ptr_ == opt))
+        throw OptionAlreadyAdded("cannot move help options");
+
+    if(config_ptr_ == opt)
+        throw OptionAlreadyAdded("cannot move config file options");
+
+    auto iterator =
+        std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; });
+    if(iterator != std::end(options_)) {
+        const auto &opt_p = *iterator;
+        if(std::find_if(std::begin(app->options_), std::end(app->options_), [&opt_p](const Option_p &v) {
+               return (*v == *opt_p);
+           }) == std::end(app->options_)) {
+            // only erase after the insertion was successful
+            app->options_.push_back(std::move(*iterator));
+            options_.erase(iterator);
+        } else {
+            throw OptionAlreadyAdded("option was not located: " + opt->get_name());
+        }
+    } else {
+        throw OptionNotFound("could not locate the given Option");
+    }
+}
+
+CLI11_INLINE void TriggerOn(App *trigger_app, App *app_to_enable) {
+    app_to_enable->enabled_by_default(false);
+    app_to_enable->disabled_by_default();
+    trigger_app->preparse_callback([app_to_enable](std::size_t) { app_to_enable->disabled(false); });
+}
+
+CLI11_INLINE void TriggerOn(App *trigger_app, std::vector<App *> apps_to_enable) {
+    for(auto &app : apps_to_enable) {
+        app->enabled_by_default(false);
+        app->disabled_by_default();
+    }
+
+    trigger_app->preparse_callback([apps_to_enable](std::size_t) {
+        for(const auto &app : apps_to_enable) {
+            app->disabled(false);
+        }
+    });
+}
+
+CLI11_INLINE void TriggerOff(App *trigger_app, App *app_to_enable) {
+    app_to_enable->disabled_by_default(false);
+    app_to_enable->enabled_by_default();
+    trigger_app->preparse_callback([app_to_enable](std::size_t) { app_to_enable->disabled(); });
+}
+
+CLI11_INLINE void TriggerOff(App *trigger_app, std::vector<App *> apps_to_enable) {
+    for(auto &app : apps_to_enable) {
+        app->disabled_by_default(false);
+        app->enabled_by_default();
+    }
+
+    trigger_app->preparse_callback([apps_to_enable](std::size_t) {
+        for(const auto &app : apps_to_enable) {
+            app->disabled();
+        }
+    });
+}
+
+CLI11_INLINE void deprecate_option(Option *opt, const std::string &replacement) {
+    Validator deprecate_warning{[opt, replacement](std::string &) {
+                                    std::cout << opt->get_name() << " is deprecated please use '" << replacement
+                                              << "' instead\n";
+                                    return std::string();
+                                },
+                                "DEPRECATED"};
+    deprecate_warning.application_index(0);
+    opt->check(deprecate_warning);
+    if(!replacement.empty()) {
+        opt->description(opt->get_description() + " DEPRECATED: please use '" + replacement + "' instead");
+    }
+}
+
+CLI11_INLINE void retire_option(App *app, Option *opt) {
+    App temp;
+    auto *option_copy = temp.add_option(opt->get_name(false, true))
+                            ->type_size(opt->get_type_size_min(), opt->get_type_size_max())
+                            ->expected(opt->get_expected_min(), opt->get_expected_max())
+                            ->allow_extra_args(opt->get_allow_extra_args());
+
+    app->remove_option(opt);
+    auto *opt2 = app->add_option(option_copy->get_name(false, true), "option has been retired and has no effect")
+                     ->type_name("RETIRED")
+                     ->default_str("RETIRED")
+                     ->type_size(option_copy->get_type_size_min(), option_copy->get_type_size_max())
+                     ->expected(option_copy->get_expected_min(), option_copy->get_expected_max())
+                     ->allow_extra_args(option_copy->get_allow_extra_args());
+
+    Validator retired_warning{[opt2](std::string &) {
+                                  std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n";
+                                  return std::string();
+                              },
+                              ""};
+    retired_warning.application_index(0);
+    opt2->check(retired_warning);
+}
+
+CLI11_INLINE void retire_option(App &app, Option *opt) { retire_option(&app, opt); }
+
+CLI11_INLINE void retire_option(App *app, const std::string &option_name) {
+
+    auto *opt = app->get_option_no_throw(option_name);
+    if(opt != nullptr) {
+        retire_option(app, opt);
+        return;
+    }
+    auto *opt2 = app->add_option(option_name, "option has been retired and has no effect")
+                     ->type_name("RETIRED")
+                     ->expected(0, 1)
+                     ->default_str("RETIRED");
+    Validator retired_warning{[opt2](std::string &) {
+                                  std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n";
+                                  return std::string();
+                              },
+                              ""};
+    retired_warning.application_index(0);
+    opt2->check(retired_warning);
+}
+
+CLI11_INLINE void retire_option(App &app, const std::string &option_name) { retire_option(&app, option_name); }
+
+namespace FailureMessage {
+
+CLI11_INLINE std::string simple(const App *app, const Error &e) {
+    std::string header = std::string(e.what()) + "\n";
+    std::vector<std::string> names;
+
+    // Collect names
+    if(app->get_help_ptr() != nullptr)
+        names.push_back(app->get_help_ptr()->get_name());
+
+    if(app->get_help_all_ptr() != nullptr)
+        names.push_back(app->get_help_all_ptr()->get_name());
+
+    // If any names found, suggest those
+    if(!names.empty())
+        header += "Run with " + detail::join(names, " or ") + " for more information.\n";
+
+    return header;
+}
+
+CLI11_INLINE std::string help(const App *app, const Error &e) {
+    std::string header = std::string("ERROR: ") + e.get_name() + ": " + e.what() + "\n";
+    header += app->help();
+    return header;
+}
+
+}  // namespace FailureMessage
+
+// [CLI11:app_inl_hpp:end]
+}  // namespace CLI
diff --git a/include/CLI/Config.hpp b/include/CLI/impl/Config_inl.hpp
similarity index 90%
rename from include/CLI/Config.hpp
rename to include/CLI/impl/Config_inl.hpp
index 57944ec..8021d5f 100644
--- a/include/CLI/Config.hpp
+++ b/include/CLI/impl/Config_inl.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -6,24 +6,22 @@
 
 #pragma once
 
+// This include is only needed for IDEs to discover symbols
+#include <CLI/Config.hpp>
+
 // [CLI11:public_includes:set]
 #include <algorithm>
-#include <fstream>
-#include <iostream>
 #include <string>
 #include <utility>
 #include <vector>
-// [CLI11:public_includes:set]
-
-#include "App.hpp"
-#include "ConfigFwd.hpp"
-#include "StringTools.hpp"
+// [CLI11:public_includes:end]
 
 namespace CLI {
-// [CLI11:config_hpp:verbatim]
+// [CLI11:config_inl_hpp:verbatim]
+
 namespace detail {
 
-inline std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\'') {
+CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char stringQuote, char characterQuote) {
     if(arg.empty()) {
         return std::string(2, stringQuote);
     }
@@ -33,8 +31,9 @@ inline std::string convert_arg_for_ini(const std::string &arg, char stringQuote
     }
     // floating point conversion can convert some hex codes, but don't try that here
     if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) {
-        double val;
-        if(detail::lexical_cast(arg, val)) {
+        using CLI::detail::lexical_cast;
+        double val = 0.0;
+        if(lexical_cast(arg, val)) {
             return arg;
         }
     }
@@ -62,18 +61,16 @@ inline std::string convert_arg_for_ini(const std::string &arg, char stringQuote
     }
     if(arg.find_first_of(stringQuote) == std::string::npos) {
         return std::string(1, stringQuote) + arg + stringQuote;
-    } else {
-        return characterQuote + arg + characterQuote;
     }
+    return characterQuote + arg + characterQuote;
 }
 
-/// Comma separated join, adds quotes if needed
-inline std::string ini_join(const std::vector<std::string> &args,
-                            char sepChar = ',',
-                            char arrayStart = '[',
-                            char arrayEnd = ']',
-                            char stringQuote = '"',
-                            char characterQuote = '\'') {
+CLI11_INLINE std::string ini_join(const std::vector<std::string> &args,
+                                  char sepChar,
+                                  char arrayStart,
+                                  char arrayEnd,
+                                  char stringQuote,
+                                  char characterQuote) {
     std::string joined;
     if(args.size() > 1 && arrayStart != '\0') {
         joined.push_back(arrayStart);
@@ -82,7 +79,7 @@ inline std::string ini_join(const std::vector<std::string> &args,
     for(const auto &arg : args) {
         if(start++ > 0) {
             joined.push_back(sepChar);
-            if(isspace(sepChar) == 0) {
+            if(!std::isspace<char>(sepChar, std::locale())) {
                 joined.push_back(' ');
             }
         }
@@ -94,7 +91,8 @@ inline std::string ini_join(const std::vector<std::string> &args,
     return joined;
 }
 
-inline std::vector<std::string> generate_parents(const std::string &section, std::string &name, char parentSeparator) {
+CLI11_INLINE std::vector<std::string>
+generate_parents(const std::string &section, std::string &name, char parentSeparator) {
     std::vector<std::string> parents;
     if(detail::to_lower(section) != "default") {
         if(section.find(parentSeparator) != std::string::npos) {
@@ -118,8 +116,7 @@ inline std::vector<std::string> generate_parents(const std::string &section, std
     return parents;
 }
 
-/// assuming non default segments do a check on the close and open of the segments in a configItem structure
-inline void
+CLI11_INLINE void
 checkParentSegments(std::vector<ConfigItem> &output, const std::string &currentSection, char parentSeparator) {
 
     std::string estring;
@@ -230,7 +227,7 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
             std::string item = detail::trim_copy(line.substr(pos + 1));
             auto cloc = item.find(commentChar);
             if(cloc != std::string::npos) {
-                item.erase(cloc, std::string::npos);
+                item.erase(cloc, std::string::npos);  // NOLINT(readability-suspicious-call-argument)
                 detail::trim(item);
             }
             if(item.size() > 1 && item.front() == aStart) {
@@ -250,7 +247,7 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
             name = detail::trim_copy(line);
             auto cloc = name.find(commentChar);
             if(cloc != std::string::npos) {
-                name.erase(cloc, std::string::npos);
+                name.erase(cloc, std::string::npos);  // NOLINT(readability-suspicious-call-argument)
                 detail::trim(name);
             }
 
@@ -301,7 +298,7 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
     return output;
 }
 
-inline std::string
+CLI11_INLINE std::string
 ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
     std::stringstream out;
     std::string commentLead;
@@ -348,6 +345,9 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
                 }
 
                 if(!value.empty()) {
+                    if(!opt->get_fnames().empty()) {
+                        value = opt->get_flag_value(name, value);
+                    }
                     if(write_description && opt->has_description()) {
                         out << '\n';
                         out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
@@ -374,7 +374,7 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
                     out << '[' << prefix << subcom->get_name() << "]\n";
                 } else {
                     std::string subname = app->get_name() + parentSeparatorChar + subcom->get_name();
-                    auto p = app->get_parent();
+                    const auto *p = app->get_parent();
                     while(p->get_parent() != nullptr) {
                         subname = p->get_name() + parentSeparatorChar + subname;
                         p = p->get_parent();
@@ -391,6 +391,5 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
 
     return out.str();
 }
-
-// [CLI11:config_hpp:end]
+// [CLI11:config_inl_hpp:end]
 }  // namespace CLI
diff --git a/include/CLI/Formatter.hpp b/include/CLI/impl/Formatter_inl.hpp
similarity index 85%
rename from include/CLI/Formatter.hpp
rename to include/CLI/impl/Formatter_inl.hpp
index e45aa25..541cd34 100644
--- a/include/CLI/Formatter.hpp
+++ b/include/CLI/impl/Formatter_inl.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -6,19 +6,19 @@
 
 #pragma once
 
+// This include is only needed for IDEs to discover symbols
+#include <CLI/Formatter.hpp>
+
 // [CLI11:public_includes:set]
 #include <algorithm>
 #include <string>
+#include <utility>
 #include <vector>
 // [CLI11:public_includes:end]
 
-#include "App.hpp"
-#include "FormatterFwd.hpp"
-
 namespace CLI {
-// [CLI11:formatter_hpp:verbatim]
-
-inline std::string
+// [CLI11:formatter_inl_hpp:verbatim]
+CLI11_INLINE std::string
 Formatter::make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const {
     std::stringstream out;
 
@@ -30,17 +30,17 @@ Formatter::make_group(std::string group, bool is_positional, std::vector<const O
     return out.str();
 }
 
-inline std::string Formatter::make_positionals(const App *app) const {
+CLI11_INLINE std::string Formatter::make_positionals(const App *app) const {
     std::vector<const Option *> opts =
         app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); });
 
     if(opts.empty())
-        return std::string();
+        return {};
 
     return make_group(get_label("Positionals"), true, opts);
 }
 
-inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) const {
+CLI11_INLINE std::string Formatter::make_groups(const App *app, AppFormatMode mode) const {
     std::stringstream out;
     std::vector<std::string> groups = app->get_groups();
 
@@ -64,7 +64,7 @@ inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) co
     return out.str();
 }
 
-inline std::string Formatter::make_description(const App *app) const {
+CLI11_INLINE std::string Formatter::make_description(const App *app) const {
     std::string desc = app->get_description();
     auto min_options = app->get_require_option_min();
     auto max_options = app->get_require_option_max();
@@ -75,7 +75,7 @@ inline std::string Formatter::make_description(const App *app) const {
         if(min_options == 1) {
             desc += " \n[Exactly 1 of the following options is required]";
         } else {
-            desc += " \n[Exactly " + std::to_string(min_options) + "options from the following list are required]";
+            desc += " \n[Exactly " + std::to_string(min_options) + " options from the following list are required]";
         }
     } else if(max_options > 0) {
         if(min_options > 0) {
@@ -90,7 +90,7 @@ inline std::string Formatter::make_description(const App *app) const {
     return (!desc.empty()) ? desc + "\n" : std::string{};
 }
 
-inline std::string Formatter::make_usage(const App *app, std::string name) const {
+CLI11_INLINE std::string Formatter::make_usage(const App *app, std::string name) const {
     std::stringstream out;
 
     out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name;
@@ -132,15 +132,15 @@ inline std::string Formatter::make_usage(const App *app, std::string name) const
     return out.str();
 }
 
-inline std::string Formatter::make_footer(const App *app) const {
+CLI11_INLINE std::string Formatter::make_footer(const App *app) const {
     std::string footer = app->get_footer();
     if(footer.empty()) {
         return std::string{};
     }
-    return footer + "\n";
+    return "\n" + footer + "\n";
 }
 
-inline std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const {
+CLI11_INLINE std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const {
 
     // This immediately forwards to the make_expanded method. This is done this way so that subcommands can
     // have overridden formatters
@@ -159,12 +159,12 @@ inline std::string Formatter::make_help(const App *app, std::string name, AppFor
     out << make_positionals(app);
     out << make_groups(app, mode);
     out << make_subcommands(app, mode);
-    out << '\n' << make_footer(app);
+    out << make_footer(app);
 
     return out.str();
 }
 
-inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const {
+CLI11_INLINE std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const {
     std::stringstream out;
 
     std::vector<const App *> subcommands = app->get_subcommands({});
@@ -206,13 +206,13 @@ inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mod
     return out.str();
 }
 
-inline std::string Formatter::make_subcommand(const App *sub) const {
+CLI11_INLINE std::string Formatter::make_subcommand(const App *sub) const {
     std::stringstream out;
     detail::format_help(out, sub->get_display_name(true), sub->get_description(), column_width_);
     return out.str();
 }
 
-inline std::string Formatter::make_expanded(const App *sub) const {
+CLI11_INLINE std::string Formatter::make_expanded(const App *sub) const {
     std::stringstream out;
     out << sub->get_display_name(true) << "\n";
 
@@ -232,14 +232,14 @@ inline std::string Formatter::make_expanded(const App *sub) const {
     return detail::find_and_replace(tmp, "\n", "\n  ") + "\n";
 }
 
-inline std::string Formatter::make_option_name(const Option *opt, bool is_positional) const {
+CLI11_INLINE std::string Formatter::make_option_name(const Option *opt, bool is_positional) const {
     if(is_positional)
         return opt->get_name(true, false);
 
     return opt->get_name(false, true);
 }
 
-inline std::string Formatter::make_option_opts(const Option *opt) const {
+CLI11_INLINE std::string Formatter::make_option_opts(const Option *opt) const {
     std::stringstream out;
 
     if(!opt->get_option_text().empty()) {
@@ -249,7 +249,7 @@ inline std::string Formatter::make_option_opts(const Option *opt) const {
             if(!opt->get_type_name().empty())
                 out << " " << get_label(opt->get_type_name());
             if(!opt->get_default_str().empty())
-                out << "=" << opt->get_default_str();
+                out << " [" << opt->get_default_str() << "] ";
             if(opt->get_expected_max() == detail::expected_max_vector_size)
                 out << " ...";
             else if(opt->get_expected_min() > 1)
@@ -274,9 +274,9 @@ inline std::string Formatter::make_option_opts(const Option *opt) const {
     return out.str();
 }
 
-inline std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); }
+CLI11_INLINE std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); }
 
-inline std::string Formatter::make_option_usage(const Option *opt) const {
+CLI11_INLINE std::string Formatter::make_option_usage(const Option *opt) const {
     // Note that these are positionals usages
     std::stringstream out;
     out << make_option_name(opt, true);
@@ -287,6 +287,5 @@ inline std::string Formatter::make_option_usage(const Option *opt) const {
 
     return opt->get_required() ? out.str() : "[" + out.str() + "]";
 }
-
-// [CLI11:formatter_hpp:end]
+// [CLI11:formatter_inl_hpp:end]
 }  // namespace CLI
diff --git a/include/CLI/impl/Option_inl.hpp b/include/CLI/impl/Option_inl.hpp
new file mode 100644
index 0000000..a24df9a
--- /dev/null
+++ b/include/CLI/impl/Option_inl.hpp
@@ -0,0 +1,653 @@
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// This include is only needed for IDEs to discover symbols
+#include <CLI/Option.hpp>
+
+// [CLI11:public_includes:set]
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+namespace CLI {
+// [CLI11:option_inl_hpp:verbatim]
+
+template <typename CRTP> template <typename T> void OptionBase<CRTP>::copy_to(T *other) const {
+    other->group(group_);
+    other->required(required_);
+    other->ignore_case(ignore_case_);
+    other->ignore_underscore(ignore_underscore_);
+    other->configurable(configurable_);
+    other->disable_flag_override(disable_flag_override_);
+    other->delimiter(delimiter_);
+    other->always_capture_default(always_capture_default_);
+    other->multi_option_policy(multi_option_policy_);
+}
+
+CLI11_INLINE Option *Option::expected(int value) {
+    if(value < 0) {
+        expected_min_ = -value;
+        if(expected_max_ < expected_min_) {
+            expected_max_ = expected_min_;
+        }
+        allow_extra_args_ = true;
+        flag_like_ = false;
+    } else if(value == detail::expected_max_vector_size) {
+        expected_min_ = 1;
+        expected_max_ = detail::expected_max_vector_size;
+        allow_extra_args_ = true;
+        flag_like_ = false;
+    } else {
+        expected_min_ = value;
+        expected_max_ = value;
+        flag_like_ = (expected_min_ == 0);
+    }
+    return this;
+}
+
+CLI11_INLINE Option *Option::expected(int value_min, int value_max) {
+    if(value_min < 0) {
+        value_min = -value_min;
+    }
+
+    if(value_max < 0) {
+        value_max = detail::expected_max_vector_size;
+    }
+    if(value_max < value_min) {
+        expected_min_ = value_max;
+        expected_max_ = value_min;
+    } else {
+        expected_max_ = value_max;
+        expected_min_ = value_min;
+    }
+
+    return this;
+}
+
+CLI11_INLINE Option *Option::check(Validator validator, const std::string &validator_name) {
+    validator.non_modifying();
+    validators_.push_back(std::move(validator));
+    if(!validator_name.empty())
+        validators_.back().name(validator_name);
+    return this;
+}
+
+CLI11_INLINE Option *Option::check(std::function<std::string(const std::string &)> Validator,
+                                   std::string Validator_description,
+                                   std::string Validator_name) {
+    validators_.emplace_back(Validator, std::move(Validator_description), std::move(Validator_name));
+    validators_.back().non_modifying();
+    return this;
+}
+
+CLI11_INLINE Option *Option::transform(Validator Validator, const std::string &Validator_name) {
+    validators_.insert(validators_.begin(), std::move(Validator));
+    if(!Validator_name.empty())
+        validators_.front().name(Validator_name);
+    return this;
+}
+
+CLI11_INLINE Option *Option::transform(const std::function<std::string(std::string)> &func,
+                                       std::string transform_description,
+                                       std::string transform_name) {
+    validators_.insert(validators_.begin(),
+                       Validator(
+                           [func](std::string &val) {
+                               val = func(val);
+                               return std::string{};
+                           },
+                           std::move(transform_description),
+                           std::move(transform_name)));
+
+    return this;
+}
+
+CLI11_INLINE Option *Option::each(const std::function<void(std::string)> &func) {
+    validators_.emplace_back(
+        [func](std::string &inout) {
+            func(inout);
+            return std::string{};
+        },
+        std::string{});
+    return this;
+}
+
+CLI11_INLINE Validator *Option::get_validator(const std::string &Validator_name) {
+    for(auto &Validator : validators_) {
+        if(Validator_name == Validator.get_name()) {
+            return &Validator;
+        }
+    }
+    if((Validator_name.empty()) && (!validators_.empty())) {
+        return &(validators_.front());
+    }
+    throw OptionNotFound(std::string{"Validator "} + Validator_name + " Not Found");
+}
+
+CLI11_INLINE Validator *Option::get_validator(int index) {
+    // This is an signed int so that it is not equivalent to a pointer.
+    if(index >= 0 && index < static_cast<int>(validators_.size())) {
+        return &(validators_[static_cast<decltype(validators_)::size_type>(index)]);
+    }
+    throw OptionNotFound("Validator index is not valid");
+}
+
+CLI11_INLINE bool Option::remove_needs(Option *opt) {
+    auto iterator = std::find(std::begin(needs_), std::end(needs_), opt);
+
+    if(iterator == std::end(needs_)) {
+        return false;
+    }
+    needs_.erase(iterator);
+    return true;
+}
+
+CLI11_INLINE Option *Option::excludes(Option *opt) {
+    if(opt == this) {
+        throw(IncorrectConstruction("and option cannot exclude itself"));
+    }
+    excludes_.insert(opt);
+
+    // Help text should be symmetric - excluding a should exclude b
+    opt->excludes_.insert(this);
+
+    // Ignoring the insert return value, excluding twice is now allowed.
+    // (Mostly to allow both directions to be excluded by user, even though the library does it for you.)
+
+    return this;
+}
+
+CLI11_INLINE bool Option::remove_excludes(Option *opt) {
+    auto iterator = std::find(std::begin(excludes_), std::end(excludes_), opt);
+
+    if(iterator == std::end(excludes_)) {
+        return false;
+    }
+    excludes_.erase(iterator);
+    return true;
+}
+
+template <typename T> Option *Option::ignore_case(bool value) {
+    if(!ignore_case_ && value) {
+        ignore_case_ = value;
+        auto *parent = static_cast<T *>(parent_);
+        for(const Option_p &opt : parent->options_) {
+            if(opt.get() == this) {
+                continue;
+            }
+            const auto &omatch = opt->matching_name(*this);
+            if(!omatch.empty()) {
+                ignore_case_ = false;
+                throw OptionAlreadyAdded("adding ignore case caused a name conflict with " + omatch);
+            }
+        }
+    } else {
+        ignore_case_ = value;
+    }
+    return this;
+}
+
+template <typename T> Option *Option::ignore_underscore(bool value) {
+
+    if(!ignore_underscore_ && value) {
+        ignore_underscore_ = value;
+        auto *parent = static_cast<T *>(parent_);
+        for(const Option_p &opt : parent->options_) {
+            if(opt.get() == this) {
+                continue;
+            }
+            const auto &omatch = opt->matching_name(*this);
+            if(!omatch.empty()) {
+                ignore_underscore_ = false;
+                throw OptionAlreadyAdded("adding ignore underscore caused a name conflict with " + omatch);
+            }
+        }
+    } else {
+        ignore_underscore_ = value;
+    }
+    return this;
+}
+
+CLI11_INLINE Option *Option::multi_option_policy(MultiOptionPolicy value) {
+    if(value != multi_option_policy_) {
+        if(multi_option_policy_ == MultiOptionPolicy::Throw && expected_max_ == detail::expected_max_vector_size &&
+           expected_min_ > 1) {  // this bizarre condition is to maintain backwards compatibility
+                                 // with the previous behavior of expected_ with vectors
+            expected_max_ = expected_min_;
+        }
+        multi_option_policy_ = value;
+        current_option_state_ = option_state::parsing;
+    }
+    return this;
+}
+
+CLI11_NODISCARD CLI11_INLINE std::string Option::get_name(bool positional, bool all_options) const {
+    if(get_group().empty())
+        return {};  // Hidden
+
+    if(all_options) {
+
+        std::vector<std::string> name_list;
+
+        /// The all list will never include a positional unless asked or that's the only name.
+        if((positional && (!pname_.empty())) || (snames_.empty() && lnames_.empty())) {
+            name_list.push_back(pname_);
+        }
+        if((get_items_expected() == 0) && (!fnames_.empty())) {
+            for(const std::string &sname : snames_) {
+                name_list.push_back("-" + sname);
+                if(check_fname(sname)) {
+                    name_list.back() += "{" + get_flag_value(sname, "") + "}";
+                }
+            }
+
+            for(const std::string &lname : lnames_) {
+                name_list.push_back("--" + lname);
+                if(check_fname(lname)) {
+                    name_list.back() += "{" + get_flag_value(lname, "") + "}";
+                }
+            }
+        } else {
+            for(const std::string &sname : snames_)
+                name_list.push_back("-" + sname);
+
+            for(const std::string &lname : lnames_)
+                name_list.push_back("--" + lname);
+        }
+
+        return detail::join(name_list);
+    }
+
+    // This returns the positional name no matter what
+    if(positional)
+        return pname_;
+
+    // Prefer long name
+    if(!lnames_.empty())
+        return std::string(2, '-') + lnames_[0];
+
+    // Or short name if no long name
+    if(!snames_.empty())
+        return std::string(1, '-') + snames_[0];
+
+    // If positional is the only name, it's okay to use that
+    return pname_;
+}
+
+CLI11_INLINE void Option::run_callback() {
+    if(force_callback_ && results_.empty()) {
+        add_result(default_str_);
+    }
+    if(current_option_state_ == option_state::parsing) {
+        _validate_results(results_);
+        current_option_state_ = option_state::validated;
+    }
+
+    if(current_option_state_ < option_state::reduced) {
+        _reduce_results(proc_results_, results_);
+        current_option_state_ = option_state::reduced;
+    }
+    if(current_option_state_ >= option_state::reduced) {
+        current_option_state_ = option_state::callback_run;
+        if(!(callback_)) {
+            return;
+        }
+        const results_t &send_results = proc_results_.empty() ? results_ : proc_results_;
+        bool local_result = callback_(send_results);
+
+        if(!local_result)
+            throw ConversionError(get_name(), results_);
+    }
+}
+
+CLI11_NODISCARD CLI11_INLINE const std::string &Option::matching_name(const Option &other) const {
+    static const std::string estring;
+    for(const std::string &sname : snames_)
+        if(other.check_sname(sname))
+            return sname;
+    for(const std::string &lname : lnames_)
+        if(other.check_lname(lname))
+            return lname;
+
+    if(ignore_case_ ||
+       ignore_underscore_) {  // We need to do the inverse, in case we are ignore_case or ignore underscore
+        for(const std::string &sname : other.snames_)
+            if(check_sname(sname))
+                return sname;
+        for(const std::string &lname : other.lnames_)
+            if(check_lname(lname))
+                return lname;
+    }
+    return estring;
+}
+
+CLI11_NODISCARD CLI11_INLINE bool Option::check_name(const std::string &name) const {
+
+    if(name.length() > 2 && name[0] == '-' && name[1] == '-')
+        return check_lname(name.substr(2));
+    if(name.length() > 1 && name.front() == '-')
+        return check_sname(name.substr(1));
+    if(!pname_.empty()) {
+        std::string local_pname = pname_;
+        std::string local_name = name;
+        if(ignore_underscore_) {
+            local_pname = detail::remove_underscore(local_pname);
+            local_name = detail::remove_underscore(local_name);
+        }
+        if(ignore_case_) {
+            local_pname = detail::to_lower(local_pname);
+            local_name = detail::to_lower(local_name);
+        }
+        if(local_name == local_pname) {
+            return true;
+        }
+    }
+
+    if(!envname_.empty()) {
+        // this needs to be the original since envname_ shouldn't match on case insensitivity
+        return (name == envname_);
+    }
+    return false;
+}
+
+CLI11_NODISCARD CLI11_INLINE std::string Option::get_flag_value(const std::string &name,
+                                                                std::string input_value) const {
+    static const std::string trueString{"true"};
+    static const std::string falseString{"false"};
+    static const std::string emptyString{"{}"};
+    // check for disable flag override_
+    if(disable_flag_override_) {
+        if(!((input_value.empty()) || (input_value == emptyString))) {
+            auto default_ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
+            if(default_ind >= 0) {
+                // We can static cast this to std::size_t because it is more than 0 in this block
+                if(default_flag_values_[static_cast<std::size_t>(default_ind)].second != input_value) {
+                    throw(ArgumentMismatch::FlagOverride(name));
+                }
+            } else {
+                if(input_value != trueString) {
+                    throw(ArgumentMismatch::FlagOverride(name));
+                }
+            }
+        }
+    }
+    auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
+    if((input_value.empty()) || (input_value == emptyString)) {
+        if(flag_like_) {
+            return (ind < 0) ? trueString : default_flag_values_[static_cast<std::size_t>(ind)].second;
+        }
+        return (ind < 0) ? default_str_ : default_flag_values_[static_cast<std::size_t>(ind)].second;
+    }
+    if(ind < 0) {
+        return input_value;
+    }
+    if(default_flag_values_[static_cast<std::size_t>(ind)].second == falseString) {
+        try {
+            auto val = detail::to_flag_value(input_value);
+            return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
+        } catch(const std::invalid_argument &) {
+            return input_value;
+        }
+    } else {
+        return input_value;
+    }
+}
+
+CLI11_INLINE Option *Option::add_result(std::string s) {
+    _add_result(std::move(s), results_);
+    current_option_state_ = option_state::parsing;
+    return this;
+}
+
+CLI11_INLINE Option *Option::add_result(std::string s, int &results_added) {
+    results_added = _add_result(std::move(s), results_);
+    current_option_state_ = option_state::parsing;
+    return this;
+}
+
+CLI11_INLINE Option *Option::add_result(std::vector<std::string> s) {
+    current_option_state_ = option_state::parsing;
+    for(auto &str : s) {
+        _add_result(std::move(str), results_);
+    }
+    return this;
+}
+
+CLI11_NODISCARD CLI11_INLINE results_t Option::reduced_results() const {
+    results_t res = proc_results_.empty() ? results_ : proc_results_;
+    if(current_option_state_ < option_state::reduced) {
+        if(current_option_state_ == option_state::parsing) {
+            res = results_;
+            _validate_results(res);
+        }
+        if(!res.empty()) {
+            results_t extra;
+            _reduce_results(extra, res);
+            if(!extra.empty()) {
+                res = std::move(extra);
+            }
+        }
+    }
+    return res;
+}
+
+CLI11_INLINE Option *Option::type_size(int option_type_size) {
+    if(option_type_size < 0) {
+        // this section is included for backwards compatibility
+        type_size_max_ = -option_type_size;
+        type_size_min_ = -option_type_size;
+        expected_max_ = detail::expected_max_vector_size;
+    } else {
+        type_size_max_ = option_type_size;
+        if(type_size_max_ < detail::expected_max_vector_size) {
+            type_size_min_ = option_type_size;
+        } else {
+            inject_separator_ = true;
+        }
+        if(type_size_max_ == 0)
+            required_ = false;
+    }
+    return this;
+}
+
+CLI11_INLINE Option *Option::type_size(int option_type_size_min, int option_type_size_max) {
+    if(option_type_size_min < 0 || option_type_size_max < 0) {
+        // this section is included for backwards compatibility
+        expected_max_ = detail::expected_max_vector_size;
+        option_type_size_min = (std::abs)(option_type_size_min);
+        option_type_size_max = (std::abs)(option_type_size_max);
+    }
+
+    if(option_type_size_min > option_type_size_max) {
+        type_size_max_ = option_type_size_min;
+        type_size_min_ = option_type_size_max;
+    } else {
+        type_size_min_ = option_type_size_min;
+        type_size_max_ = option_type_size_max;
+    }
+    if(type_size_max_ == 0) {
+        required_ = false;
+    }
+    if(type_size_max_ >= detail::expected_max_vector_size) {
+        inject_separator_ = true;
+    }
+    return this;
+}
+
+CLI11_NODISCARD CLI11_INLINE std::string Option::get_type_name() const {
+    std::string full_type_name = type_name_();
+    if(!validators_.empty()) {
+        for(const auto &Validator : validators_) {
+            std::string vtype = Validator.get_description();
+            if(!vtype.empty()) {
+                full_type_name += ":" + vtype;
+            }
+        }
+    }
+    return full_type_name;
+}
+
+CLI11_INLINE void Option::_validate_results(results_t &res) const {
+    // Run the Validators (can change the string)
+    if(!validators_.empty()) {
+        if(type_size_max_ > 1) {  // in this context index refers to the index in the type
+            int index = 0;
+            if(get_items_expected_max() < static_cast<int>(res.size()) &&
+               multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
+                // create a negative index for the earliest ones
+                index = get_items_expected_max() - static_cast<int>(res.size());
+            }
+
+            for(std::string &result : res) {
+                if(detail::is_separator(result) && type_size_max_ != type_size_min_ && index >= 0) {
+                    index = 0;  // reset index for variable size chunks
+                    continue;
+                }
+                auto err_msg = _validate(result, (index >= 0) ? (index % type_size_max_) : index);
+                if(!err_msg.empty())
+                    throw ValidationError(get_name(), err_msg);
+                ++index;
+            }
+        } else {
+            int index = 0;
+            if(expected_max_ < static_cast<int>(res.size()) &&
+               multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
+                // create a negative index for the earliest ones
+                index = expected_max_ - static_cast<int>(res.size());
+            }
+            for(std::string &result : res) {
+                auto err_msg = _validate(result, index);
+                ++index;
+                if(!err_msg.empty())
+                    throw ValidationError(get_name(), err_msg);
+            }
+        }
+    }
+}
+
+CLI11_INLINE void Option::_reduce_results(results_t &out, const results_t &original) const {
+
+    // max num items expected or length of vector, always at least 1
+    // Only valid for a trimming policy
+
+    out.clear();
+    // Operation depends on the policy setting
+    switch(multi_option_policy_) {
+    case MultiOptionPolicy::TakeAll:
+        break;
+    case MultiOptionPolicy::TakeLast: {
+        // Allow multi-option sizes (including 0)
+        std::size_t trim_size = std::min<std::size_t>(
+            static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
+        if(original.size() != trim_size) {
+            out.assign(original.end() - static_cast<results_t::difference_type>(trim_size), original.end());
+        }
+    } break;
+    case MultiOptionPolicy::TakeFirst: {
+        std::size_t trim_size = std::min<std::size_t>(
+            static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
+        if(original.size() != trim_size) {
+            out.assign(original.begin(), original.begin() + static_cast<results_t::difference_type>(trim_size));
+        }
+    } break;
+    case MultiOptionPolicy::Join:
+        if(results_.size() > 1) {
+            out.push_back(detail::join(original, std::string(1, (delimiter_ == '\0') ? '\n' : delimiter_)));
+        }
+        break;
+    case MultiOptionPolicy::Sum:
+        out.push_back(detail::sum_string_vector(original));
+        break;
+    case MultiOptionPolicy::Throw:
+    default: {
+        auto num_min = static_cast<std::size_t>(get_items_expected_min());
+        auto num_max = static_cast<std::size_t>(get_items_expected_max());
+        if(num_min == 0) {
+            num_min = 1;
+        }
+        if(num_max == 0) {
+            num_max = 1;
+        }
+        if(original.size() < num_min) {
+            throw ArgumentMismatch::AtLeast(get_name(), static_cast<int>(num_min), original.size());
+        }
+        if(original.size() > num_max) {
+            throw ArgumentMismatch::AtMost(get_name(), static_cast<int>(num_max), original.size());
+        }
+        break;
+    }
+    }
+    // this check is to allow an empty vector in certain circumstances but not if expected is not zero.
+    // {} is the indicator for an empty container
+    if(out.empty()) {
+        if(original.size() == 1 && original[0] == "{}" && get_items_expected_min() > 0) {
+            out.push_back("{}");
+            out.push_back("%%");
+        }
+    } else if(out.size() == 1 && out[0] == "{}" && get_items_expected_min() > 0) {
+        out.push_back("%%");
+    }
+}
+
+CLI11_INLINE std::string Option::_validate(std::string &result, int index) const {
+    std::string err_msg;
+    if(result.empty() && expected_min_ == 0) {
+        // an empty with nothing expected is allowed
+        return err_msg;
+    }
+    for(const auto &vali : validators_) {
+        auto v = vali.get_application_index();
+        if(v == -1 || v == index) {
+            try {
+                err_msg = vali(result);
+            } catch(const ValidationError &err) {
+                err_msg = err.what();
+            }
+            if(!err_msg.empty())
+                break;
+        }
+    }
+
+    return err_msg;
+}
+
+CLI11_INLINE int Option::_add_result(std::string &&result, std::vector<std::string> &res) const {
+    int result_count = 0;
+    if(allow_extra_args_ && !result.empty() && result.front() == '[' &&
+       result.back() == ']') {  // this is now a vector string likely from the default or user entry
+        result.pop_back();
+
+        for(auto &var : CLI::detail::split(result.substr(1), ',')) {
+            if(!var.empty()) {
+                result_count += _add_result(std::move(var), res);
+            }
+        }
+        return result_count;
+    }
+    if(delimiter_ == '\0') {
+        res.push_back(std::move(result));
+        ++result_count;
+    } else {
+        if((result.find_first_of(delimiter_) != std::string::npos)) {
+            for(const auto &var : CLI::detail::split(result, delimiter_)) {
+                if(!var.empty()) {
+                    res.push_back(var);
+                    ++result_count;
+                }
+            }
+        } else {
+            res.push_back(std::move(result));
+            ++result_count;
+        }
+    }
+    return result_count;
+}
+// [CLI11:option_inl_hpp:end]
+}  // namespace CLI
diff --git a/include/CLI/Split.hpp b/include/CLI/impl/Split_inl.hpp
similarity index 71%
rename from include/CLI/Split.hpp
rename to include/CLI/impl/Split_inl.hpp
index 83ef572..bb05a86 100644
--- a/include/CLI/Split.hpp
+++ b/include/CLI/impl/Split_inl.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -6,6 +6,9 @@
 
 #pragma once
 
+// This include is only needed for IDEs to discover symbols
+#include <CLI/Split.hpp>
+
 // [CLI11:public_includes:set]
 #include <string>
 #include <tuple>
@@ -13,16 +16,15 @@
 #include <vector>
 // [CLI11:public_includes:end]
 
-#include "Error.hpp"
-#include "StringTools.hpp"
+#include <CLI/Error.hpp>
+#include <CLI/StringTools.hpp>
 
 namespace CLI {
-// [CLI11:split_hpp:verbatim]
+// [CLI11:split_inl_hpp:verbatim]
 
 namespace detail {
 
-// Returns false if not a short option. Otherwise, sets opt name and rest and returns true
-inline bool split_short(const std::string &current, std::string &name, std::string &rest) {
+CLI11_INLINE bool split_short(const std::string &current, std::string &name, std::string &rest) {
     if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) {
         name = current.substr(1, 1);
         rest = current.substr(2);
@@ -31,8 +33,7 @@ inline bool split_short(const std::string &current, std::string &name, std::stri
     return false;
 }
 
-// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true
-inline bool split_long(const std::string &current, std::string &name, std::string &value) {
+CLI11_INLINE bool split_long(const std::string &current, std::string &name, std::string &value) {
     if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) {
         auto loc = current.find_first_of('=');
         if(loc != std::string::npos) {
@@ -47,8 +48,7 @@ inline bool split_long(const std::string &current, std::string &name, std::strin
     return false;
 }
 
-// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true
-inline bool split_windows_style(const std::string &current, std::string &name, std::string &value) {
+CLI11_INLINE bool split_windows_style(const std::string &current, std::string &name, std::string &value) {
     if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) {
         auto loc = current.find_first_of(':');
         if(loc != std::string::npos) {
@@ -63,11 +63,10 @@ inline bool split_windows_style(const std::string &current, std::string &name, s
     return false;
 }
 
-// Splits a string into multiple long and short names
-inline std::vector<std::string> split_names(std::string current) {
+CLI11_INLINE std::vector<std::string> split_names(std::string current) {
     std::vector<std::string> output;
-    std::size_t val;
-    while((val = current.find(",")) != std::string::npos) {
+    std::size_t val = 0;
+    while((val = current.find(',')) != std::string::npos) {
         output.push_back(trim_copy(current.substr(0, val)));
         current = current.substr(val + 1);
     }
@@ -75,8 +74,7 @@ inline std::vector<std::string> split_names(std::string current) {
     return output;
 }
 
-/// extract default flag values either {def} or starting with a !
-inline std::vector<std::pair<std::string, std::string>> get_default_flag_values(const std::string &str) {
+CLI11_INLINE std::vector<std::pair<std::string, std::string>> get_default_flag_values(const std::string &str) {
     std::vector<std::string> flags = split_names(str);
     flags.erase(std::remove_if(flags.begin(),
                                flags.end(),
@@ -94,7 +92,7 @@ inline std::vector<std::pair<std::string, std::string>> get_default_flag_values(
         if((def_start != std::string::npos) && (flag.back() == '}')) {
             defval = flag.substr(def_start + 1);
             defval.pop_back();
-            flag.erase(def_start, std::string::npos);
+            flag.erase(def_start, std::string::npos);  // NOLINT(readability-suspicious-call-argument)
         }
         flag.erase(0, flag.find_first_not_of("-!"));
         output.emplace_back(flag, defval);
@@ -102,8 +100,7 @@ inline std::vector<std::pair<std::string, std::string>> get_default_flag_values(
     return output;
 }
 
-/// Get a vector of short names, one of long names, and a single name
-inline std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>
+CLI11_INLINE std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>
 get_names(const std::vector<std::string> &input) {
 
     std::vector<std::string> short_names;
@@ -134,10 +131,9 @@ get_names(const std::vector<std::string> &input) {
         }
     }
 
-    return std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>(
-        short_names, long_names, pos_name);
+    return std::make_tuple(short_names, long_names, pos_name);
 }
 
 }  // namespace detail
-// [CLI11:split_hpp:end]
+// [CLI11:split_inl_hpp:end]
 }  // namespace CLI
diff --git a/include/CLI/impl/StringTools_inl.hpp b/include/CLI/impl/StringTools_inl.hpp
new file mode 100644
index 0000000..9b81fbd
--- /dev/null
+++ b/include/CLI/impl/StringTools_inl.hpp
@@ -0,0 +1,260 @@
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// This include is only needed for IDEs to discover symbols
+#include <CLI/StringTools.hpp>
+
+// [CLI11:public_includes:set]
+#include <string>
+#include <vector>
+// [CLI11:public_includes:end]
+
+namespace CLI {
+// [CLI11:string_tools_inl_hpp:verbatim]
+
+namespace detail {
+CLI11_INLINE std::vector<std::string> split(const std::string &s, char delim) {
+    std::vector<std::string> elems;
+    // Check to see if empty string, give consistent result
+    if(s.empty()) {
+        elems.emplace_back();
+    } else {
+        std::stringstream ss;
+        ss.str(s);
+        std::string item;
+        while(std::getline(ss, item, delim)) {
+            elems.push_back(item);
+        }
+    }
+    return elems;
+}
+
+CLI11_INLINE std::string &ltrim(std::string &str) {
+    auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
+    str.erase(str.begin(), it);
+    return str;
+}
+
+CLI11_INLINE std::string &ltrim(std::string &str, const std::string &filter) {
+    auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
+    str.erase(str.begin(), it);
+    return str;
+}
+
+CLI11_INLINE std::string &rtrim(std::string &str) {
+    auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
+    str.erase(it.base(), str.end());
+    return str;
+}
+
+CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter) {
+    auto it =
+        std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
+    str.erase(it.base(), str.end());
+    return str;
+}
+
+CLI11_INLINE std::string &remove_quotes(std::string &str) {
+    if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) {
+        if(str.front() == str.back()) {
+            str.pop_back();
+            str.erase(str.begin(), str.begin() + 1);
+        }
+    }
+    return str;
+}
+
+CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input) {
+    std::string::size_type n = 0;
+    while(n != std::string::npos && n < input.size()) {
+        n = input.find('\n', n);
+        if(n != std::string::npos) {
+            input = input.substr(0, n + 1) + leader + input.substr(n + 1);
+            n += leader.size();
+        }
+    }
+    return input;
+}
+
+CLI11_INLINE std::ostream &
+format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) {
+    name = "  " + name;
+    out << std::setw(static_cast<int>(wid)) << std::left << name;
+    if(!description.empty()) {
+        if(name.length() >= wid)
+            out << "\n" << std::setw(static_cast<int>(wid)) << "";
+        for(const char c : description) {
+            out.put(c);
+            if(c == '\n') {
+                out << std::setw(static_cast<int>(wid)) << "";
+            }
+        }
+    }
+    out << "\n";
+    return out;
+}
+
+CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid) {
+    if(!aliases.empty()) {
+        out << std::setw(static_cast<int>(wid)) << "     aliases: ";
+        bool front = true;
+        for(const auto &alias : aliases) {
+            if(!front) {
+                out << ", ";
+            } else {
+                front = false;
+            }
+            out << detail::fix_newlines("              ", alias);
+        }
+        out << "\n";
+    }
+    return out;
+}
+
+CLI11_INLINE bool valid_name_string(const std::string &str) {
+    if(str.empty() || !valid_first_char(str[0])) {
+        return false;
+    }
+    auto e = str.end();
+    for(auto c = str.begin() + 1; c != e; ++c)
+        if(!valid_later_char(*c))
+            return false;
+    return true;
+}
+
+CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to) {
+
+    std::size_t start_pos = 0;
+
+    while((start_pos = str.find(from, start_pos)) != std::string::npos) {
+        str.replace(start_pos, from.length(), to);
+        start_pos += to.length();
+    }
+
+    return str;
+}
+
+CLI11_INLINE void remove_default_flag_values(std::string &flags) {
+    auto loc = flags.find_first_of('{', 2);
+    while(loc != std::string::npos) {
+        auto finish = flags.find_first_of("},", loc + 1);
+        if((finish != std::string::npos) && (flags[finish] == '}')) {
+            flags.erase(flags.begin() + static_cast<std::ptrdiff_t>(loc),
+                        flags.begin() + static_cast<std::ptrdiff_t>(finish) + 1);
+        }
+        loc = flags.find_first_of('{', loc + 1);
+    }
+    flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
+}
+
+CLI11_INLINE std::ptrdiff_t
+find_member(std::string name, const std::vector<std::string> names, bool ignore_case, bool ignore_underscore) {
+    auto it = std::end(names);
+    if(ignore_case) {
+        if(ignore_underscore) {
+            name = detail::to_lower(detail::remove_underscore(name));
+            it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
+                return detail::to_lower(detail::remove_underscore(local_name)) == name;
+            });
+        } else {
+            name = detail::to_lower(name);
+            it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
+                return detail::to_lower(local_name) == name;
+            });
+        }
+
+    } else if(ignore_underscore) {
+        name = detail::remove_underscore(name);
+        it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
+            return detail::remove_underscore(local_name) == name;
+        });
+    } else {
+        it = std::find(std::begin(names), std::end(names), name);
+    }
+
+    return (it != std::end(names)) ? (it - std::begin(names)) : (-1);
+}
+
+CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter) {
+
+    const std::string delims("\'\"`");
+    auto find_ws = [delimiter](char ch) {
+        return (delimiter == '\0') ? std::isspace<char>(ch, std::locale()) : (ch == delimiter);
+    };
+    trim(str);
+
+    std::vector<std::string> output;
+    bool embeddedQuote = false;
+    char keyChar = ' ';
+    while(!str.empty()) {
+        if(delims.find_first_of(str[0]) != std::string::npos) {
+            keyChar = str[0];
+            auto end = str.find_first_of(keyChar, 1);
+            while((end != std::string::npos) && (str[end - 1] == '\\')) {  // deal with escaped quotes
+                end = str.find_first_of(keyChar, end + 1);
+                embeddedQuote = true;
+            }
+            if(end != std::string::npos) {
+                output.push_back(str.substr(1, end - 1));
+                if(end + 2 < str.size()) {
+                    str = str.substr(end + 2);
+                } else {
+                    str.clear();
+                }
+
+            } else {
+                output.push_back(str.substr(1));
+                str = "";
+            }
+        } else {
+            auto it = std::find_if(std::begin(str), std::end(str), find_ws);
+            if(it != std::end(str)) {
+                std::string value = std::string(str.begin(), it);
+                output.push_back(value);
+                str = std::string(it + 1, str.end());
+            } else {
+                output.push_back(str);
+                str = "";
+            }
+        }
+        // transform any embedded quotes into the regular character
+        if(embeddedQuote) {
+            output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar));
+            embeddedQuote = false;
+        }
+        trim(str);
+    }
+    return output;
+}
+
+CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) {
+    auto next = str[offset + 1];
+    if((next == '\"') || (next == '\'') || (next == '`')) {
+        auto astart = str.find_last_of("-/ \"\'`", offset - 1);
+        if(astart != std::string::npos) {
+            if(str[astart] == ((str[offset] == '=') ? '-' : '/'))
+                str[offset] = ' ';  // interpret this as a space so the split_up works properly
+        }
+    }
+    return offset + 1;
+}
+
+CLI11_INLINE std::string &add_quotes_if_needed(std::string &str) {
+    if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) {
+        char quote = str.find('"') < str.find('\'') ? '\'' : '"';
+        if(str.find(' ') != std::string::npos) {
+            str.insert(0, 1, quote);
+            str.append(1, quote);
+        }
+    }
+    return str;
+}
+
+}  // namespace detail
+// [CLI11:string_tools_inl_hpp:end]
+}  // namespace CLI
diff --git a/include/CLI/impl/Validators_inl.hpp b/include/CLI/impl/Validators_inl.hpp
new file mode 100644
index 0000000..d6ac4fd
--- /dev/null
+++ b/include/CLI/impl/Validators_inl.hpp
@@ -0,0 +1,347 @@
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+#include <CLI/Validators.hpp>
+
+#include <CLI/Macros.hpp>
+#include <CLI/StringTools.hpp>
+#include <CLI/TypeTools.hpp>
+
+// [CLI11:public_includes:set]
+#include <map>
+#include <string>
+#include <utility>
+// [CLI11:public_includes:end]
+
+namespace CLI {
+// [CLI11:validators_inl_hpp:verbatim]
+
+CLI11_INLINE std::string Validator::operator()(std::string &str) const {
+    std::string retstring;
+    if(active_) {
+        if(non_modifying_) {
+            std::string value = str;
+            retstring = func_(value);
+        } else {
+            retstring = func_(str);
+        }
+    }
+    return retstring;
+}
+
+CLI11_NODISCARD CLI11_INLINE Validator Validator::description(std::string validator_desc) const {
+    Validator newval(*this);
+    newval.desc_function_ = [validator_desc]() { return validator_desc; };
+    return newval;
+}
+
+CLI11_INLINE Validator Validator::operator&(const Validator &other) const {
+    Validator newval;
+
+    newval._merge_description(*this, other, " AND ");
+
+    // Give references (will make a copy in lambda function)
+    const std::function<std::string(std::string & filename)> &f1 = func_;
+    const std::function<std::string(std::string & filename)> &f2 = other.func_;
+
+    newval.func_ = [f1, f2](std::string &input) {
+        std::string s1 = f1(input);
+        std::string s2 = f2(input);
+        if(!s1.empty() && !s2.empty())
+            return std::string("(") + s1 + ") AND (" + s2 + ")";
+        return s1 + s2;
+    };
+
+    newval.active_ = active_ && other.active_;
+    newval.application_index_ = application_index_;
+    return newval;
+}
+
+CLI11_INLINE Validator Validator::operator|(const Validator &other) const {
+    Validator newval;
+
+    newval._merge_description(*this, other, " OR ");
+
+    // Give references (will make a copy in lambda function)
+    const std::function<std::string(std::string &)> &f1 = func_;
+    const std::function<std::string(std::string &)> &f2 = other.func_;
+
+    newval.func_ = [f1, f2](std::string &input) {
+        std::string s1 = f1(input);
+        std::string s2 = f2(input);
+        if(s1.empty() || s2.empty())
+            return std::string();
+
+        return std::string("(") + s1 + ") OR (" + s2 + ")";
+    };
+    newval.active_ = active_ && other.active_;
+    newval.application_index_ = application_index_;
+    return newval;
+}
+
+CLI11_INLINE Validator Validator::operator!() const {
+    Validator newval;
+    const std::function<std::string()> &dfunc1 = desc_function_;
+    newval.desc_function_ = [dfunc1]() {
+        auto str = dfunc1();
+        return (!str.empty()) ? std::string("NOT ") + str : std::string{};
+    };
+    // Give references (will make a copy in lambda function)
+    const std::function<std::string(std::string & res)> &f1 = func_;
+
+    newval.func_ = [f1, dfunc1](std::string &test) -> std::string {
+        std::string s1 = f1(test);
+        if(s1.empty()) {
+            return std::string("check ") + dfunc1() + " succeeded improperly";
+        }
+        return std::string{};
+    };
+    newval.active_ = active_;
+    newval.application_index_ = application_index_;
+    return newval;
+}
+
+CLI11_INLINE void
+Validator::_merge_description(const Validator &val1, const Validator &val2, const std::string &merger) {
+
+    const std::function<std::string()> &dfunc1 = val1.desc_function_;
+    const std::function<std::string()> &dfunc2 = val2.desc_function_;
+
+    desc_function_ = [=]() {
+        std::string f1 = dfunc1();
+        std::string f2 = dfunc2();
+        if((f1.empty()) || (f2.empty())) {
+            return f1 + f2;
+        }
+        return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')';
+    };
+}
+
+namespace detail {
+
+#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
+CLI11_INLINE path_type check_path(const char *file) noexcept {
+    std::error_code ec;
+    auto stat = std::filesystem::status(file, ec);
+    if(ec) {
+        return path_type::nonexistent;
+    }
+    switch(stat.type()) {
+    case std::filesystem::file_type::none:  // LCOV_EXCL_LINE
+    case std::filesystem::file_type::not_found:
+        return path_type::nonexistent;
+    case std::filesystem::file_type::directory:
+        return path_type::directory;
+    case std::filesystem::file_type::symlink:
+    case std::filesystem::file_type::block:
+    case std::filesystem::file_type::character:
+    case std::filesystem::file_type::fifo:
+    case std::filesystem::file_type::socket:
+    case std::filesystem::file_type::regular:
+    case std::filesystem::file_type::unknown:
+    default:
+        return path_type::file;
+    }
+}
+#else
+CLI11_INLINE path_type check_path(const char *file) noexcept {
+#if defined(_MSC_VER)
+    struct __stat64 buffer;
+    if(_stat64(file, &buffer) == 0) {
+        return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file;
+    }
+#else
+    struct stat buffer;
+    if(stat(file, &buffer) == 0) {
+        return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file;
+    }
+#endif
+    return path_type::nonexistent;
+}
+#endif
+
+CLI11_INLINE ExistingFileValidator::ExistingFileValidator() : Validator("FILE") {
+    func_ = [](std::string &filename) {
+        auto path_result = check_path(filename.c_str());
+        if(path_result == path_type::nonexistent) {
+            return "File does not exist: " + filename;
+        }
+        if(path_result == path_type::directory) {
+            return "File is actually a directory: " + filename;
+        }
+        return std::string();
+    };
+}
+
+CLI11_INLINE ExistingDirectoryValidator::ExistingDirectoryValidator() : Validator("DIR") {
+    func_ = [](std::string &filename) {
+        auto path_result = check_path(filename.c_str());
+        if(path_result == path_type::nonexistent) {
+            return "Directory does not exist: " + filename;
+        }
+        if(path_result == path_type::file) {
+            return "Directory is actually a file: " + filename;
+        }
+        return std::string();
+    };
+}
+
+CLI11_INLINE ExistingPathValidator::ExistingPathValidator() : Validator("PATH(existing)") {
+    func_ = [](std::string &filename) {
+        auto path_result = check_path(filename.c_str());
+        if(path_result == path_type::nonexistent) {
+            return "Path does not exist: " + filename;
+        }
+        return std::string();
+    };
+}
+
+CLI11_INLINE NonexistentPathValidator::NonexistentPathValidator() : Validator("PATH(non-existing)") {
+    func_ = [](std::string &filename) {
+        auto path_result = check_path(filename.c_str());
+        if(path_result != path_type::nonexistent) {
+            return "Path already exists: " + filename;
+        }
+        return std::string();
+    };
+}
+
+CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") {
+    func_ = [](std::string &ip_addr) {
+        auto result = CLI::detail::split(ip_addr, '.');
+        if(result.size() != 4) {
+            return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')';
+        }
+        int num = 0;
+        for(const auto &var : result) {
+            using CLI::detail::lexical_cast;
+            bool retval = lexical_cast(var, num);
+            if(!retval) {
+                return std::string("Failed parsing number (") + var + ')';
+            }
+            if(num < 0 || num > 255) {
+                return std::string("Each IP number must be between 0 and 255 ") + var;
+            }
+        }
+        return std::string();
+    };
+}
+
+}  // namespace detail
+
+CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn)
+    : Validator("FILE") {
+    func_ = [default_path, enableErrorReturn](std::string &filename) {
+        auto path_result = detail::check_path(filename.c_str());
+        if(path_result == detail::path_type::nonexistent) {
+            std::string test_file_path = default_path;
+            if(default_path.back() != '/' && default_path.back() != '\\') {
+                // Add folder separator
+                test_file_path += '/';
+            }
+            test_file_path.append(filename);
+            path_result = detail::check_path(test_file_path.c_str());
+            if(path_result == detail::path_type::file) {
+                filename = test_file_path;
+            } else {
+                if(enableErrorReturn) {
+                    return "File does not exist: " + filename;
+                }
+            }
+        }
+        return std::string{};
+    };
+}
+
+CLI11_INLINE AsSizeValue::AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) {
+    if(kb_is_1000) {
+        description("SIZE [b, kb(=1000b), kib(=1024b), ...]");
+    } else {
+        description("SIZE [b, kb(=1024b), ...]");
+    }
+}
+
+CLI11_INLINE std::map<std::string, AsSizeValue::result_t> AsSizeValue::init_mapping(bool kb_is_1000) {
+    std::map<std::string, result_t> m;
+    result_t k_factor = kb_is_1000 ? 1000 : 1024;
+    result_t ki_factor = 1024;
+    result_t k = 1;
+    result_t ki = 1;
+    m["b"] = 1;
+    for(std::string p : {"k", "m", "g", "t", "p", "e"}) {
+        k *= k_factor;
+        ki *= ki_factor;
+        m[p] = k;
+        m[p + "b"] = k;
+        m[p + "i"] = ki;
+        m[p + "ib"] = ki;
+    }
+    return m;
+}
+
+CLI11_INLINE std::map<std::string, AsSizeValue::result_t> AsSizeValue::get_mapping(bool kb_is_1000) {
+    if(kb_is_1000) {
+        static auto m = init_mapping(true);
+        return m;
+    }
+    static auto m = init_mapping(false);
+    return m;
+}
+
+namespace detail {
+
+CLI11_INLINE std::pair<std::string, std::string> split_program_name(std::string commandline) {
+    // try to determine the programName
+    std::pair<std::string, std::string> vals;
+    trim(commandline);
+    auto esp = commandline.find_first_of(' ', 1);
+    while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) {
+        esp = commandline.find_first_of(' ', esp + 1);
+        if(esp == std::string::npos) {
+            // if we have reached the end and haven't found a valid file just assume the first argument is the
+            // program name
+            if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') {
+                bool embeddedQuote = false;
+                auto keyChar = commandline[0];
+                auto end = commandline.find_first_of(keyChar, 1);
+                while((end != std::string::npos) && (commandline[end - 1] == '\\')) {  // deal with escaped quotes
+                    end = commandline.find_first_of(keyChar, end + 1);
+                    embeddedQuote = true;
+                }
+                if(end != std::string::npos) {
+                    vals.first = commandline.substr(1, end - 1);
+                    esp = end + 1;
+                    if(embeddedQuote) {
+                        vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar));
+                    }
+                } else {
+                    esp = commandline.find_first_of(' ', 1);
+                }
+            } else {
+                esp = commandline.find_first_of(' ', 1);
+            }
+
+            break;
+        }
+    }
+    if(vals.first.empty()) {
+        vals.first = commandline.substr(0, esp);
+        rtrim(vals.first);
+    }
+
+    // strip the program name
+    vals.second = (esp < commandline.length() - 1) ? commandline.substr(esp + 1) : std::string{};
+    ltrim(vals.second);
+    return vals;
+}
+
+}  // namespace detail
+/// @}
+
+// [CLI11:validators_inl_hpp:end]
+}  // namespace CLI
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 0000000..dd4654d
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1 @@
+option('tests', type: 'boolean', value: false, description: 'Build CLI11 tests')
diff --git a/scripts/MakeSingleHeader.py b/scripts/MakeSingleHeader.py
index 42e0ee7..4bc53dd 100755
--- a/scripts/MakeSingleHeader.py
+++ b/scripts/MakeSingleHeader.py
@@ -4,7 +4,7 @@ from __future__ import print_function, unicode_literals
 
 import os
 import re
-from argparse import ArgumentParser
+import argparse
 from subprocess import Popen, PIPE
 import warnings
 
@@ -115,6 +115,9 @@ def make_header(output, main_header, files, tag, namespace, macro=None, version=
         print("Converting macros", before, "->", after)
         single_header.replace(before, after)
 
+    new_namespace = namespace + "::"
+    single_header = re.sub(r"\bCLI::\b", new_namespace, single_header)
+
     if output is not None:
         with open(output, "w") as f:
             f.write(single_header)
@@ -125,8 +128,9 @@ def make_header(output, main_header, files, tag, namespace, macro=None, version=
 
 
 if __name__ == "__main__":
-    parser = ArgumentParser(
-        usage="Convert source to single header include. Can optionally add namespace and search-replace replacements (for macros)."
+    parser = argparse.ArgumentParser(
+        usage="Convert source to single header include. Can optionally add namespace and search-replace replacements (for macros).",
+        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
     )
     parser.add_argument("--output", default=None, help="Single header file output")
     parser.add_argument(
@@ -134,7 +138,7 @@ if __name__ == "__main__":
         default="CLI11.hpp.in",
         help="The main include file that defines the other files",
     )
-    parser.add_argument("files", nargs="*", help="The header files")
+    parser.add_argument("files", nargs="+", help="The header files")
     parser.add_argument("--namespace", default="CLI", help="Set the namespace")
     parser.add_argument("--tag", default="CLI11", help="Tag to look up")
     parser.add_argument(
diff --git a/scripts/mdlint_style.rb b/scripts/mdlint_style.rb
index 6fca85b..5cb3a97 100644
--- a/scripts/mdlint_style.rb
+++ b/scripts/mdlint_style.rb
@@ -6,3 +6,4 @@ exclude_rule 'MD034'  # Bare URL (for now)
 
 rule 'MD026', punctuation: '.,;:!'  # Trailing punctuation in header (& in this case)
 rule 'MD029', style: :ordered
+rule 'MD007', indent: 2
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..9409b86
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,122 @@
+set(CLI11_headerLoc "${PROJECT_SOURCE_DIR}/include/CLI")
+
+set(CLI11_headers
+    ${CLI11_headerLoc}/App.hpp
+    ${CLI11_headerLoc}/Config.hpp
+    ${CLI11_headerLoc}/ConfigFwd.hpp
+    ${CLI11_headerLoc}/Error.hpp
+    ${CLI11_headerLoc}/Formatter.hpp
+    ${CLI11_headerLoc}/FormatterFwd.hpp
+    ${CLI11_headerLoc}/Macros.hpp
+    ${CLI11_headerLoc}/Option.hpp
+    ${CLI11_headerLoc}/Split.hpp
+    ${CLI11_headerLoc}/StringTools.hpp
+    ${CLI11_headerLoc}/TypeTools.hpp
+    ${CLI11_headerLoc}/Validators.hpp
+    ${CLI11_headerLoc}/Version.hpp)
+
+set(CLI11_implLoc "${PROJECT_SOURCE_DIR}/include/CLI/impl")
+
+set(CLI11_impl_headers
+    ${CLI11_implLoc}/App_inl.hpp
+    ${CLI11_implLoc}/Config_inl.hpp
+    ${CLI11_implLoc}/Formatter_inl.hpp
+    ${CLI11_implLoc}/Option_inl.hpp
+    ${CLI11_implLoc}/Split_inl.hpp
+    ${CLI11_implLoc}/StringTools_inl.hpp
+    ${CLI11_implLoc}/Validators_inl.hpp)
+
+set(CLI11_library_headers ${CLI11_headerLoc}/CLI.hpp ${CLI11_headerLoc}/Timer.hpp)
+
+if(CLI11_PRECOMPILED)
+  # Create static lib
+  file(GLOB CLI11_precompile_sources "${PROJECT_SOURCE_DIR}/src/*.cpp")
+  add_library(CLI11 STATIC ${CLI11_headers} ${CLI11_library_headers} ${CLI11_impl_headers}
+                           ${CLI11_precompile_sources})
+  target_compile_definitions(CLI11 PUBLIC -DCLI11_COMPILE)
+
+  set(PUBLIC_OR_INTERFACE PUBLIC)
+else()
+  add_library(CLI11 INTERFACE)
+  if(CMAKE_VERSION VERSION_GREATER 3.19)
+    # This is only useful for visual studio and other IDE builds
+    target_sources(CLI11 PRIVATE ${CLI11_headers} ${CLI11_library_headers} ${CLI11_impl_headers})
+  endif()
+
+  set(PUBLIC_OR_INTERFACE INTERFACE)
+endif()
+
+# Allow IDE's to group targets into folders
+add_library(CLI11::CLI11 ALIAS CLI11) # for add_subdirectory calls
+
+# Duplicated because CMake adds the current source dir if you don't.
+target_include_directories(
+  CLI11 ${PUBLIC_OR_INTERFACE} $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
+  $<INSTALL_INTERFACE:include>)
+
+if(CMAKE_VERSION VERSION_LESS 3.8)
+  # This might not be a complete list
+  target_compile_features(
+    CLI11
+    INTERFACE cxx_lambdas
+              cxx_nullptr
+              cxx_override
+              cxx_range_for
+              cxx_right_angle_brackets
+              cxx_strong_enums
+              cxx_constexpr
+              cxx_auto_type)
+else()
+  target_compile_features(CLI11 INTERFACE cxx_std_11)
+endif()
+
+if(CLI11_SINGLE_FILE)
+  # Single file test
+  if(CMAKE_VERSION VERSION_LESS 3.12)
+    find_package(PythonInterp REQUIRED)
+    add_executable(Python::Interpreter IMPORTED)
+    set_target_properties(Python::Interpreter PROPERTIES IMPORTED_LOCATION "${PYTHON_EXECUTABLE}"
+                                                         VERSION "${PYTHON_VERSION_STRING}")
+  else()
+    find_package(
+      Python
+      COMPONENTS Interpreter
+      REQUIRED)
+  endif()
+
+  file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/include")
+  add_custom_command(
+    OUTPUT "${PROJECT_BINARY_DIR}/include/CLI11.hpp"
+    COMMAND
+      Python::Interpreter "${PROJECT_SOURCE_DIR}/scripts/MakeSingleHeader.py" ${CLI11_headers}
+      ${CLI11_impl_headers} --main "${PROJECT_SOURCE_DIR}/CLI11.hpp.in" --output
+      "${PROJECT_BINARY_DIR}/include/CLI11.hpp" --version "${CLI11_VERSION}"
+    DEPENDS "${PROJECT_SOURCE_DIR}/include/CLI/CLI.hpp" ${CLI11_headers} ${CLI11_impl_headers})
+  add_custom_target(CLI11-generate-single-file ALL
+                    DEPENDS "${PROJECT_BINARY_DIR}/include/CLI11.hpp")
+  set_property(TARGET CLI11-generate-single-file PROPERTY FOLDER "Scripts")
+  if(CLI11_INSTALL)
+    install(FILES "${PROJECT_BINARY_DIR}/include/CLI11.hpp"
+            DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
+  endif()
+  add_library(CLI11_SINGLE INTERFACE)
+  target_link_libraries(CLI11_SINGLE INTERFACE CLI11)
+  add_dependencies(CLI11_SINGLE CLI11-generate-single-file)
+  target_compile_definitions(CLI11_SINGLE INTERFACE -DCLI11_SINGLE_FILE)
+  target_include_directories(
+    CLI11_SINGLE INTERFACE $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include/>
+                           $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
+endif()
+
+if(CLI11_INSTALL)
+
+  # Make an export target
+  install(TARGETS CLI11 EXPORT CLI11Targets)
+  if(NOT CLI11_SINGLE_FILE)
+    install(FILES ${CLI11_headers} ${CLI11_library_headers}
+            DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/CLI")
+    if(NOT CLI11_COMPILE)
+      install(FILES ${CLI11_impl_headers} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/CLI/impl")
+    endif()
+  endif()
+endif()
diff --git a/src/Precompile.cpp b/src/Precompile.cpp
new file mode 100644
index 0000000..81900ce
--- /dev/null
+++ b/src/Precompile.cpp
@@ -0,0 +1,13 @@
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/impl/App_inl.hpp>
+#include <CLI/impl/Config_inl.hpp>
+#include <CLI/impl/Formatter_inl.hpp>
+#include <CLI/impl/Option_inl.hpp>
+#include <CLI/impl/Split_inl.hpp>
+#include <CLI/impl/StringTools_inl.hpp>
+#include <CLI/impl/Validators_inl.hpp>
diff --git a/subprojects/catch2.wrap b/subprojects/catch2.wrap
new file mode 100644
index 0000000..ce0b2a9
--- /dev/null
+++ b/subprojects/catch2.wrap
@@ -0,0 +1,11 @@
+[wrap-file]
+directory = Catch2-2.13.7
+source_url = https://github.com/catchorg/Catch2/archive/v2.13.7.zip
+source_filename = Catch2-2.13.7.zip
+source_hash = 3f3ccd90ad3a8fbb1beeb15e6db440ccdcbebe378dfd125d07a1f9a587a927e9
+patch_filename = catch2_2.13.7-1_patch.zip
+patch_url = https://wrapdb.mesonbuild.com/v2/catch2_2.13.7-1/get_patch
+patch_hash = 2f7369645d747e5bd866317ac1dd4c3d04dc97d3aad4fc6b864bdf75d3b57158
+
+[provide]
+catch2 = catch2_dep
diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp
index d425b41..de90f64 100644
--- a/tests/AppTest.cpp
+++ b/tests/AppTest.cpp
@@ -1,10 +1,12 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
 // SPDX-License-Identifier: BSD-3-Clause
 
 #include "app_helper.hpp"
+#include <cmath>
+
 #include <complex>
 #include <cstdint>
 #include <cstdlib>
@@ -30,10 +32,10 @@ TEST_CASE_METHOD(TApp, "OneFlagShortValues", "[app]") {
 }
 
 TEST_CASE_METHOD(TApp, "OneFlagShortValuesAs", "[app]") {
-    auto flg = app.add_flag("-c{1},--count{2}");
+    auto *flg = app.add_flag("-c{1},--count{2}");
     args = {"-c"};
     run();
-    auto opt = app["-c"];
+    const auto *opt = app["-c"];
     CHECK(1 == opt->as<int>());
     args = {"--count"};
     run();
@@ -68,8 +70,8 @@ TEST_CASE_METHOD(TApp, "OneFlagShortWindows", "[app]") {
 TEST_CASE_METHOD(TApp, "WindowsLongShortMix1", "[app]") {
     app.allow_windows_style_options();
 
-    auto a = app.add_flag("-c");
-    auto b = app.add_flag("--c");
+    auto *a = app.add_flag("-c");
+    auto *b = app.add_flag("--c");
     args = {"/c"};
     run();
     CHECK(a->count() == 1u);
@@ -79,8 +81,8 @@ TEST_CASE_METHOD(TApp, "WindowsLongShortMix1", "[app]") {
 TEST_CASE_METHOD(TApp, "WindowsLongShortMix2", "[app]") {
     app.allow_windows_style_options();
 
-    auto a = app.add_flag("--c");
-    auto b = app.add_flag("-c");
+    auto *a = app.add_flag("--c");
+    auto *b = app.add_flag("-c");
     args = {"/c"};
     run();
     CHECK(a->count() == 1u);
@@ -160,7 +162,7 @@ TEST_CASE_METHOD(TApp, "RequireOptionsError", "[app]") {
 
 TEST_CASE_METHOD(TApp, "BoolFlagOverride", "[app]") {
     bool val{false};
-    auto flg = app.add_flag("--this,--that", val);
+    auto *flg = app.add_flag("--this,--that", val);
 
     app.parse("--this");
     CHECK(val);
@@ -200,7 +202,7 @@ TEST_CASE_METHOD(TApp, "OneFlagRefValue", "[app]") {
 
 TEST_CASE_METHOD(TApp, "OneFlagRefValueFalse", "[app]") {
     int ref{0};
-    auto flg = app.add_flag("-c,--count", ref);
+    auto *flg = app.add_flag("-c,--count", ref);
     args = {"--count=false"};
     run();
     CHECK(app.count("-c") == 1u);
@@ -212,7 +214,7 @@ TEST_CASE_METHOD(TApp, "OneFlagRefValueFalse", "[app]") {
     run();
     CHECK(app.count("-c") == 1u);
     CHECK(app.count("--count") == 1u);
-    CHECK(ref == -1);
+    CHECK(ref == 0);
 
     args = {"--count=happy"};
     CHECK_THROWS_AS(run(), CLI::ConversionError);
@@ -220,7 +222,7 @@ TEST_CASE_METHOD(TApp, "OneFlagRefValueFalse", "[app]") {
 
 TEST_CASE_METHOD(TApp, "FlagNegation", "[app]") {
     int ref{0};
-    auto flg = app.add_flag("-c,--count,--ncount{false}", ref);
+    auto *flg = app.add_flag("-c,--count,--ncount{false}", ref);
     args = {"--count", "-c", "--ncount"};
     CHECK(!flg->check_fname("count"));
     CHECK(flg->check_fname("ncount"));
@@ -437,7 +439,7 @@ TEST_CASE_METHOD(TApp, "OneStringFlagLike", "[app]") {
 
 TEST_CASE_METHOD(TApp, "OneIntFlagLike", "[app]") {
     int val{0};
-    auto opt = app.add_option("-i", val)->expected(0, 1);
+    auto *opt = app.add_option("-i", val)->expected(0, 1);
     args = {"-i"};
     run();
     CHECK(app.count("-i") == 1u);
@@ -534,7 +536,7 @@ TEST_CASE_METHOD(TApp, "NumberFlags", "[app]") {
 TEST_CASE_METHOD(TApp, "DisableFlagOverrideTest", "[app]") {
 
     int val{0};
-    auto opt = app.add_flag("--1{1},--2{2},--3{3},--4{4},--5{5},--6{6}, --7{7}, --8{8}, --9{9}", val);
+    auto *opt = app.add_flag("--1{1},--2{2},--3{3},--4{4},--5{5},--6{6}, --7{7}, --8{8}, --9{9}", val);
     CHECK(!opt->get_disable_flag_override());
     opt->disable_flag_override();
     args = {"--7=5"};
@@ -609,7 +611,7 @@ TEST_CASE_METHOD(TApp, "StrangeOptionNames", "[app]") {
 
 TEST_CASE_METHOD(TApp, "FlagLikeOption", "[app]") {
     bool val{false};
-    auto opt = app.add_option("--flag", val)->type_size(0)->default_str("true");
+    auto *opt = app.add_option("--flag", val)->type_size(0)->default_str("true");
     args = {"--flag"};
     run();
     CHECK(app.count("--flag") == 1u);
@@ -625,7 +627,7 @@ TEST_CASE_METHOD(TApp, "FlagLikeOption", "[app]") {
 
 TEST_CASE_METHOD(TApp, "FlagLikeIntOption", "[app]") {
     int val{-47};
-    auto opt = app.add_option("--flag", val)->expected(0, 1);
+    auto *opt = app.add_option("--flag", val)->expected(0, 1);
     // normally some default value should be set, but this test is for some paths in the validators checks to skip
     // validation on empty string if nothing is expected
     opt->check(CLI::PositiveNumber);
@@ -678,7 +680,7 @@ TEST_CASE_METHOD(TApp, "ShortOpts", "[app]") {
 TEST_CASE_METHOD(TApp, "TwoParamTemplateOpts", "[app]") {
 
     double funnyint{0.0};
-    auto opt = app.add_option<double, unsigned int>("-y", funnyint);
+    auto *opt = app.add_option<double, unsigned int>("-y", funnyint);
 
     args = {"-y", "32"};
 
@@ -774,6 +776,42 @@ TEST_CASE_METHOD(TApp, "JoinOpt", "[app]") {
     CHECK("one\ntwo" == str);
 }
 
+TEST_CASE_METHOD(TApp, "SumOpt", "[app]") {
+
+    int val = 0;
+    app.add_option("--val", val)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
+
+    args = {"--val=1", "--val=4"};
+
+    run();
+
+    CHECK(5 == val);
+}
+
+TEST_CASE_METHOD(TApp, "SumOptFloat", "[app]") {
+
+    double val = NAN;
+    app.add_option("--val", val)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
+
+    args = {"--val=1.3", "--val=-0.7"};
+
+    run();
+
+    CHECK(0.6 == val);
+}
+
+TEST_CASE_METHOD(TApp, "SumOptString", "[app]") {
+
+    std::string val;
+    app.add_option("--val", val)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
+
+    args = {"--val=i", "--val=2"};
+
+    run();
+
+    CHECK("i2" == val);
+}
+
 TEST_CASE_METHOD(TApp, "JoinOpt2", "[app]") {
 
     std::string str;
@@ -810,7 +848,7 @@ TEST_CASE_METHOD(TApp, "TakeLastOptMulti_alternative_path", "[app]") {
 
 TEST_CASE_METHOD(TApp, "TakeLastOptMultiCheck", "[app]") {
     std::vector<int> vals;
-    auto opt = app.add_option("--long", vals)->expected(-2)->take_last();
+    auto *opt = app.add_option("--long", vals)->expected(-2)->take_last();
 
     opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0));
     opt->check((!CLI::PositiveNumber).application_index(1));
@@ -939,6 +977,42 @@ TEST_CASE_METHOD(TApp, "RequiredOptsDouble", "[app]") {
     CHECK(std::vector<std::string>({"one", "two"}) == strs);
 }
 
+TEST_CASE_METHOD(TApp, "emptyVectorReturn", "[app]") {
+
+    std::vector<std::string> strs;
+    std::vector<std::string> strs2;
+    std::vector<std::string> strs3;
+    auto *opt1 = app.add_option("--str", strs)->required()->expected(0, 2);
+    app.add_option("--str3", strs3)->expected(1, 3);
+    app.add_option("--str2", strs2);
+    args = {"--str"};
+
+    CHECK_NOTHROW(run());
+    CHECK(std::vector<std::string>({""}) == strs);
+    args = {"--str", "one", "two"};
+
+    run();
+
+    CHECK(std::vector<std::string>({"one", "two"}) == strs);
+
+    args = {"--str", "{}", "--str2", "{}"};
+
+    run();
+
+    CHECK(strs.empty());
+    CHECK(std::vector<std::string>{"{}"} == strs2);
+    opt1->default_str("{}");
+    args = {"--str"};
+
+    CHECK_NOTHROW(run());
+    CHECK(strs.empty());
+    opt1->required(false);
+    args = {"--str3", "{}"};
+
+    CHECK_NOTHROW(run());
+    CHECK_FALSE(strs3.empty());
+}
+
 TEST_CASE_METHOD(TApp, "RequiredOptsDoubleShort", "[app]") {
 
     std::vector<std::string> strs;
@@ -1031,7 +1105,7 @@ TEST_CASE_METHOD(TApp, "RequiredPositionals", "[app]") {
     sources.clear();
     run();
 
-    CHECK(0u == sources.size());
+    CHECK(sources.empty());
     CHECK("a" == dest);
 }
 
@@ -1065,7 +1139,7 @@ TEST_CASE_METHOD(TApp, "RequiredPositionalVector", "[app]") {
 // Tests positionals at end
 TEST_CASE_METHOD(TApp, "RequiredPositionalValidation", "[app]") {
     std::vector<std::string> sources;
-    int dest;  // required
+    int dest = 0;  // required
     std::string d2;
     app.add_option("src", sources);
     app.add_option("dest", dest)->required()->check(CLI::PositiveNumber);
@@ -1149,10 +1223,10 @@ TEST_CASE_METHOD(TApp, "RequiredOptsUnlimited", "[app]") {
 
     app.allow_extras(false);
     std::vector<std::string> remain;
-    auto popt = app.add_option("positional", remain);
+    auto *popt = app.add_option("positional", remain);
     run();
     CHECK(std::vector<std::string>({"one", "two"}) == strs);
-    CHECK(std::vector<std::string>() == remain);
+    CHECK(remain.empty());
 
     args = {"--str", "one", "--", "two"};
 
@@ -1200,7 +1274,7 @@ TEST_CASE_METHOD(TApp, "RequiredOptsUnlimitedShort", "[app]") {
     app.add_option("positional", remain);
     run();
     CHECK(std::vector<std::string>({"one", "two"}) == strs);
-    CHECK(std::vector<std::string>() == remain);
+    CHECK(remain.empty());
 
     args = {"-s", "one", "--", "two"};
 
@@ -1427,7 +1501,7 @@ TEST_CASE_METHOD(TApp, "Positionals", "[app]") {
 
 TEST_CASE_METHOD(TApp, "ForcedPositional", "[app]") {
     std::vector<std::string> posit;
-    auto one = app.add_flag("--one");
+    auto *one = app.add_flag("--one");
     app.add_option("posit", posit);
 
     args = {"--one", "two", "three"};
@@ -1476,6 +1550,34 @@ TEST_CASE_METHOD(TApp, "BigPositional", "[app]") {
     CHECK(vec == args);
 }
 
+TEST_CASE_METHOD(TApp, "VectorArgAndPositional", "[app]") {
+    std::vector<std::string> vec;
+    std::vector<int> ivec;
+    app.add_option("pos", vec);
+    app.add_option("--args", ivec)->check(CLI::Number);
+    app.validate_optional_arguments();
+    args = {"one"};
+
+    run();
+    CHECK(vec == args);
+
+    args = {"--args", "1", "2"};
+
+    run();
+    CHECK(ivec.size() == 2);
+    vec.clear();
+    ivec.clear();
+
+    args = {"--args", "1", "2", "one", "two"};
+    run();
+
+    CHECK(vec.size() == 2);
+    CHECK(ivec.size() == 2);
+
+    app.validate_optional_arguments(false);
+    CHECK_THROWS(run());
+}
+
 TEST_CASE_METHOD(TApp, "Reset", "[app]") {
 
     app.add_flag("--simple");
@@ -1504,7 +1606,7 @@ TEST_CASE_METHOD(TApp, "Reset", "[app]") {
 
 TEST_CASE_METHOD(TApp, "RemoveOption", "[app]") {
     app.add_flag("--one");
-    auto opt = app.add_flag("--two");
+    auto *opt = app.add_flag("--two");
 
     CHECK(app.remove_option(opt));
     CHECK(!app.remove_option(opt));
@@ -1515,8 +1617,8 @@ TEST_CASE_METHOD(TApp, "RemoveOption", "[app]") {
 }
 
 TEST_CASE_METHOD(TApp, "RemoveNeedsLinks", "[app]") {
-    auto one = app.add_flag("--one");
-    auto two = app.add_flag("--two");
+    auto *one = app.add_flag("--one");
+    auto *two = app.add_flag("--two");
 
     two->needs(one);
     one->needs(two);
@@ -1529,8 +1631,8 @@ TEST_CASE_METHOD(TApp, "RemoveNeedsLinks", "[app]") {
 }
 
 TEST_CASE_METHOD(TApp, "RemoveExcludesLinks", "[app]") {
-    auto one = app.add_flag("--one");
-    auto two = app.add_flag("--two");
+    auto *one = app.add_flag("--one");
+    auto *two = app.add_flag("--two");
 
     two->excludes(one);
     one->excludes(two);
@@ -1547,7 +1649,7 @@ TEST_CASE_METHOD(TApp, "FileNotExists", "[app]") {
     REQUIRE_NOTHROW(CLI::NonexistentPath(myfile));
 
     std::string filename;
-    auto opt = app.add_option("--file", filename)->check(CLI::NonexistentPath, "path_check");
+    auto *opt = app.add_option("--file", filename)->check(CLI::NonexistentPath, "path_check");
     args = {"--file", myfile};
 
     run();
@@ -1603,15 +1705,15 @@ TEST_CASE_METHOD(TApp, "NotFileExists", "[app]") {
 TEST_CASE_METHOD(TApp, "DefaultedResult", "[app]") {
     std::string sval = "NA";
     int ival{0};
-    auto opts = app.add_option("--string", sval)->capture_default_str();
-    auto optv = app.add_option("--val", ival);
+    auto *opts = app.add_option("--string", sval)->capture_default_str();
+    auto *optv = app.add_option("--val", ival);
     args = {};
     run();
     CHECK("NA" == sval);
     std::string nString;
     opts->results(nString);
     CHECK("NA" == nString);
-    int newIval;
+    int newIval = 0;
     // CHECK_THROWS_AS (optv->results(newIval), CLI::ConversionError);
     optv->default_str("442");
     optv->results(newIval);
@@ -1698,7 +1800,7 @@ TEST_CASE_METHOD(TApp, "NeedsMultiFlags", "[app]") {
     CLI::Option *opt1 = app.add_flag("--opt1");
     CLI::Option *opt2 = app.add_flag("--opt2");
     CLI::Option *opt3 = app.add_flag("--opt3");
-    app.add_flag("--optall")->needs(opt1, opt2, opt3);
+    app.add_flag("--optall")->needs(opt1, opt2, opt3);  // NOLINT(readability-suspicious-call-argument)
 
     run();
 
@@ -1858,6 +1960,23 @@ TEST_CASE_METHOD(TApp, "RangeDouble", "[app]") {
     run();
 }
 
+TEST_CASE_METHOD(TApp, "NonNegative", "[app]") {
+
+    std::string res;
+    /// Note that this must be a double in Range, too
+    app.add_option("--one", res)->check(CLI::NonNegativeNumber);
+
+    args = {"--one=crazy"};
+    try {
+        // this should throw
+        run();
+        CHECK(false);
+    } catch(const CLI::ValidationError &e) {
+        std::string emess = e.what();
+        CHECK(emess.size() < 70U);
+    }
+}
+
 TEST_CASE_METHOD(TApp, "typeCheck", "[app]") {
 
     /// Note that this must be a double in Range, too
@@ -1876,6 +1995,31 @@ TEST_CASE_METHOD(TApp, "typeCheck", "[app]") {
     CHECK_THROWS_AS(run(), CLI::ValidationError);
 }
 
+TEST_CASE_METHOD(TApp, "NeedsTrue", "[app]") {
+    std::string str;
+    app.add_option("-s,--string", str);
+    app.add_flag("--opt1")->check([&](const std::string &) {
+        return (str != "val_with_opt1") ? std::string("--opt1 requires --string val_with_opt1") : std::string{};
+    });
+
+    run();
+
+    args = {"--opt1"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--string", "val"};
+    run();
+
+    args = {"--string", "val", "--opt1"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--string", "val_with_opt1", "--opt1"};
+    run();
+
+    args = {"--opt1", "--string", "val_with_opt1"};  // order is not revelant
+    run();
+}
+
 // Check to make sure programmatic access to left over is available
 TEST_CASE_METHOD(TApp, "AllowExtras", "[app]") {
 
@@ -2011,20 +2155,20 @@ TEST_CASE_METHOD(TApp, "FallthroughParentFail", "[app]") {
 }
 
 TEST_CASE_METHOD(TApp, "FallthroughParents", "[app]") {
-    auto sub = app.add_subcommand("test");
+    auto *sub = app.add_subcommand("test");
     CHECK(&app == CLI::detail::AppFriend::get_fallthrough_parent(sub));
 
-    auto ssub = sub->add_subcommand("sub2");
+    auto *ssub = sub->add_subcommand("sub2");
     CHECK(sub == CLI::detail::AppFriend::get_fallthrough_parent(ssub));
 
-    auto og1 = app.add_option_group("g1");
-    auto og2 = og1->add_option_group("g2");
-    auto og3 = og2->add_option_group("g3");
+    auto *og1 = app.add_option_group("g1");
+    auto *og2 = og1->add_option_group("g2");
+    auto *og3 = og2->add_option_group("g3");
     CHECK(&app == CLI::detail::AppFriend::get_fallthrough_parent(og3));
 
-    auto ogb1 = sub->add_option_group("g1");
-    auto ogb2 = ogb1->add_option_group("g2");
-    auto ogb3 = ogb2->add_option_group("g3");
+    auto *ogb1 = sub->add_option_group("g1");
+    auto *ogb2 = ogb1->add_option_group("g2");
+    auto *ogb3 = ogb2->add_option_group("g3");
     CHECK(sub == CLI::detail::AppFriend::get_fallthrough_parent(ogb3));
 
     ogb2->name("groupb");
@@ -2043,7 +2187,7 @@ TEST_CASE_METHOD(TApp, "OptionWithDefaults", "[app]") {
 // Added to test ->transform
 TEST_CASE_METHOD(TApp, "OrderedModifyingTransforms", "[app]") {
     std::vector<std::string> val;
-    auto m = app.add_option("-m", val);
+    auto *m = app.add_option("-m", val);
     m->transform([](std::string x) { return x + "1"; });
     m->transform([](std::string x) { return x + "2"; });
 
@@ -2056,7 +2200,7 @@ TEST_CASE_METHOD(TApp, "OrderedModifyingTransforms", "[app]") {
 
 TEST_CASE_METHOD(TApp, "ThrowingTransform", "[app]") {
     std::string val;
-    auto m = app.add_option("-m,--mess", val);
+    auto *m = app.add_option("-m,--mess", val);
     m->transform([](std::string) -> std::string { throw CLI::ValidationError("My Message"); });
 
     REQUIRE_NOTHROW(run());
@@ -2078,7 +2222,7 @@ TEST_CASE_METHOD(TApp, "EachItem", "[app]") {
     std::vector<std::string> results;
     std::vector<std::string> dummy;
 
-    auto opt = app.add_option("--vec", dummy);
+    auto *opt = app.add_option("--vec", dummy);
 
     opt->each([&results](std::string item) { results.push_back(item); });
 
@@ -2149,7 +2293,7 @@ TEST_CASE_METHOD(TApp, "CustomUserSepParse", "[app]") {
 
     std::vector<int> vals{1, 2, 3};
     args = {"--idx", "1,2,3"};
-    auto opt = app.add_option("--idx", vals)->delimiter(',');
+    auto *opt = app.add_option("--idx", vals)->delimiter(',');
     run();
     CHECK(std::vector<int>({1, 2, 3}) == vals);
     std::vector<int> vals2;
@@ -2169,7 +2313,7 @@ TEST_CASE_METHOD(TApp, "DefaultUserSepParse", "[app]") {
 
     std::vector<std::string> vals;
     args = {"--idx", "1 2 3", "4 5 6"};
-    auto opt = app.add_option("--idx", vals, "");
+    auto *opt = app.add_option("--idx", vals, "");
     run();
     CHECK(std::vector<std::string>({"1 2 3", "4 5 6"}) == vals);
     opt->delimiter(',');
@@ -2193,7 +2337,7 @@ TEST_CASE_METHOD(TApp, "CustomUserSepParse2", "[app]") {
 
     std::vector<int> vals{1, 2, 3};
     args = {"--idx", "1,2,"};
-    auto opt = app.add_option("--idx", vals)->delimiter(',');
+    auto *opt = app.add_option("--idx", vals)->delimiter(',');
     run();
     CHECK(std::vector<int>({1, 2}) == vals);
 
@@ -2219,7 +2363,7 @@ TEST_CASE_METHOD(TApp, "CustomUserSepParseToggle", "[app]") {
 
     std::vector<std::string> vals;
     args = {"--idx", "1,2,3"};
-    auto opt = app.add_option("--idx", vals)->delimiter(',');
+    auto *opt = app.add_option("--idx", vals)->delimiter(',');
     run();
     CHECK(std::vector<std::string>({"1", "2", "3"}) == vals);
     opt->delimiter('\0');
@@ -2238,7 +2382,7 @@ TEST_CASE_METHOD(TApp, "CustomUserSepParse3", "[app]") {
             "1",
             ","
             "2"};
-    auto opt = app.add_option("--idx", vals)->delimiter(',');
+    auto *opt = app.add_option("--idx", vals)->delimiter(',');
     run();
     CHECK(std::vector<int>({1, 2}) == vals);
     app.remove_option(opt);
@@ -2253,7 +2397,7 @@ TEST_CASE_METHOD(TApp, "CustomUserSepParse4", "[app]") {
 
     std::vector<int> vals;
     args = {"--idx", "1,    2"};
-    auto opt = app.add_option("--idx", vals)->delimiter(',')->capture_default_str();
+    auto *opt = app.add_option("--idx", vals)->delimiter(',')->capture_default_str();
     run();
     CHECK(std::vector<int>({1, 2}) == vals);
 
@@ -2269,7 +2413,7 @@ TEST_CASE_METHOD(TApp, "CustomUserSepParse5", "[app]") {
 
     std::vector<std::string> bar;
     args = {"this", "is", "a", "test"};
-    auto opt = app.add_option("bar", bar, "bar");
+    auto *opt = app.add_option("bar", bar, "bar");
     run();
     CHECK(std::vector<std::string>({"this", "is", "a", "test"}) == bar);
 
@@ -2308,3 +2452,11 @@ TEST_CASE_METHOD(TApp, "logFormSingleDash", "[app]") {
     CHECK(veryverbose);
     CHECK(veryveryverbose);
 }
+
+TEST_CASE("C20_compile", "simple") {
+    CLI::App app{"test"};
+    auto *flag = app.add_flag("--flag", "desc");
+
+    app.parse("--flag");
+    CHECK_FALSE(flag->empty());
+}
diff --git a/tests/BoostOptionTypeTest.cpp b/tests/BoostOptionTypeTest.cpp
index 7ed096c..c3cd818 100644
--- a/tests/BoostOptionTypeTest.cpp
+++ b/tests/BoostOptionTypeTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 80c4f6a..360d57a 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -1,12 +1,10 @@
-list(APPEND CMAKE_MODULE_PATH "${CLI11_SOURCE_DIR}/cmake")
-
 if(CLI11_SANITIZERS)
   message(STATUS "Using arsenm/sanitizers-cmake")
   FetchContent_Declare(
     sanitizers
     GIT_REPOSITORY https://github.com/arsenm/sanitizers-cmake.git
     GIT_SHALLOW 1
-    GIT_TAG 99e159e)
+    GIT_TAG a6748f4)
 
   FetchContent_GetProperties(sanitizers)
 
@@ -109,7 +107,7 @@ foreach(T IN LISTS CLI11_TESTS)
   if(CLI11_CUDA_TESTS)
     set_property(SOURCE ${T}.cpp PROPERTY LANGUAGE CUDA)
   endif()
-  add_executable(${T} ${T}.cpp ${CLI11_headers})
+  add_executable(${T} ${T}.cpp)
   add_sanitizers(${T})
   if(NOT CLI11_CUDA_TESTS)
     target_link_libraries(${T} PRIVATE CLI11_warnings)
@@ -126,7 +124,7 @@ foreach(T IN LISTS CLI11_TESTS)
 endforeach()
 
 foreach(T IN LISTS CLI11_MULTIONLY_TESTS)
-  add_executable(${T} ${T}.cpp ${CLI11_headers})
+  add_executable(${T} ${T}.cpp)
   add_sanitizers(${T})
   target_link_libraries(${T} PUBLIC CLI11)
   add_catch_test(${T})
diff --git a/tests/ComplexTypeTest.cpp b/tests/ComplexTypeTest.cpp
index 4806c54..2c3e156 100644
--- a/tests/ComplexTypeTest.cpp
+++ b/tests/ComplexTypeTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -16,7 +16,7 @@ using cx = std::complex<double>;
 CLI::Option *
 add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) {
     CLI::callback_t fun = [&variable](CLI::results_t res) {
-        double x, y;
+        double x = 0, y = 0;
         bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y);
         if(worked)
             variable = cx(x, y);
@@ -89,7 +89,7 @@ template <> bool lexical_cast<std::complex<double>>(const std::string &input, st
 
     std::smatch m;
     double x{0.0}, y{0.0};
-    bool worked;
+    bool worked = false;
     std::regex_search(input, m, creg);
     if(m.size() == 9) {
         worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y);
@@ -162,8 +162,8 @@ class complex_new {
   public:
     complex_new() = default;
     complex_new(double v1, double v2) : val1_{v1}, val2_{v2} {};
-    double real() { return val1_; }
-    double imag() { return val2_; }
+    CLI11_NODISCARD double real() const { return val1_; }
+    CLI11_NODISCARD double imag() const { return val2_; }
 
   private:
     double val1_{0.0};
diff --git a/tests/ConfigFileTest.cpp b/tests/ConfigFileTest.cpp
index 61c066e..eb6af96 100644
--- a/tests/ConfigFileTest.cpp
+++ b/tests/ConfigFileTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -47,15 +47,15 @@ TEST_CASE("StringBased: convert_arg_for_ini", "[config]") {
 
 TEST_CASE("StringBased: IniJoin", "[config]") {
     std::vector<std::string> items = {"one", "two", "three four"};
-    std::string result = "\"one\" \"two\" \"three four\"";
+    std::string result = R"("one" "two" "three four")";
 
     CHECK(result == CLI::detail::ini_join(items, ' ', '\0', '\0'));
 
-    result = "[\"one\", \"two\", \"three four\"]";
+    result = R"(["one", "two", "three four"])";
 
     CHECK(result == CLI::detail::ini_join(items));
 
-    result = "{\"one\"; \"two\"; \"three four\"}";
+    result = R"({"one"; "two"; "three four"})";
 
     CHECK(result == CLI::detail::ini_join(items, ';', '{', '}'));
 }
@@ -260,7 +260,7 @@ TEST_CASE("StringBased: SpacesSections", "[config]") {
 // check function to make sure that open sections match close sections
 bool checkSections(const std::vector<CLI::ConfigItem> &output) {
     std::set<std::string> open;
-    for(auto &ci : output) {
+    for(const auto &ci : output) {
         if(ci.name == "++") {
             auto nm = ci.fullname();
             nm.pop_back();
@@ -435,6 +435,8 @@ TEST_CASE("StringBased: file_error", "[config]") {
     CHECK_THROWS_AS(CLI::ConfigINI().from_file("nonexist_file"), CLI::FileError);
 }
 
+static const int fclear1 = fileClear("TestIniTmp.ini");
+
 TEST_CASE_METHOD(TApp, "IniNotRequired", "[config]") {
 
     TempFile tmpini{"TestIniTmp.ini"};
@@ -525,7 +527,7 @@ TEST_CASE_METHOD(TApp, "IniGetNoRemaining", "[config]") {
     int two{0};
     app.add_option("--two", two);
     REQUIRE_NOTHROW(run());
-    CHECK(0u == app.remaining().size());
+    CHECK(app.remaining().empty());
 }
 
 TEST_CASE_METHOD(TApp, "IniRequiredNoDefault", "[config]") {
@@ -554,9 +556,9 @@ TEST_CASE_METHOD(TApp, "IniNotRequiredNoDefault", "[config]") {
 class EvilConfig : public CLI::Config {
   public:
     EvilConfig() = default;
-    virtual std::string to_config(const CLI::App *, bool, bool, std::string) const { throw CLI::FileError("evil"); }
+    std::string to_config(const CLI::App *, bool, bool, std::string) const override { throw CLI::FileError("evil"); }
 
-    virtual std::vector<CLI::ConfigItem> from_config(std::istream &) const { throw CLI::FileError("evil"); }
+    std::vector<CLI::ConfigItem> from_config(std::istream &) const override { throw CLI::FileError("evil"); }
 };
 
 TEST_CASE_METHOD(TApp, "IniRequiredbadConfigurator", "[config]") {
@@ -595,6 +597,8 @@ TEST_CASE_METHOD(TApp, "IniNotRequiredbadConfigurator", "[config]") {
     REQUIRE_NOTHROW(run());
 }
 
+static const int fclear2 = fileClear("TestIniTmp2.ini");
+
 TEST_CASE_METHOD(TApp, "IniNotRequiredNotDefault", "[config]") {
 
     TempFile tmpini{"TestIniTmp.ini"};
@@ -1014,16 +1018,35 @@ TEST_CASE_METHOD(TApp, "TOMLStringVector", "[config]") {
         std::ofstream out{tmptoml};
         out << "#this is a comment line\n";
         out << "[default]\n";
+        out << "zero1=[]\n";
+        out << "zero2={}\n";
+        out << "zero3={}\n";
+        out << "zero4=[\"{}\",\"\"]\n";
+        out << "nzero={}\n";
+        out << "one=[\"1\"]\n";
         out << "two=[\"2\",\"3\"]\n";
         out << "three=[\"1\",\"2\",\"3\"]\n";
     }
 
-    std::vector<std::string> two, three;
+    std::vector<std::string> nzero, zero1, zero2, zero3, zero4, one, two, three;
+    app.add_option("--zero1", zero1)->required()->expected(0, 99)->default_str("{}");
+    app.add_option("--zero2", zero2)->required()->expected(0, 99)->default_val(std::vector<std::string>{});
+    // if no default is specified the argument results in an empty string
+    app.add_option("--zero3", zero3)->required()->expected(0, 99);
+    app.add_option("--zero4", zero4)->required()->expected(0, 99);
+    app.add_option("--nzero", nzero)->required();
+    app.add_option("--one", one)->required();
     app.add_option("--two", two)->required();
     app.add_option("--three", three)->required();
 
     run();
 
+    CHECK(zero1 == std::vector<std::string>({}));
+    CHECK(zero2 == std::vector<std::string>({}));
+    CHECK(zero3 == std::vector<std::string>({""}));
+    CHECK(zero4 == std::vector<std::string>({"{}"}));
+    CHECK(nzero == std::vector<std::string>({"{}"}));
+    CHECK(one == std::vector<std::string>({"1"}));
     CHECK(two == std::vector<std::string>({"2", "3"}));
     CHECK(three == std::vector<std::string>({"1", "2", "3"}));
 }
@@ -1038,16 +1061,25 @@ TEST_CASE_METHOD(TApp, "IniVectorCsep", "[config]") {
         std::ofstream out{tmpini};
         out << "#this is a comment line\n";
         out << "[default]\n";
+        out << "zero1=[]\n";
+        out << "zero2=[]\n";
+        out << "one=[1]\n";
         out << "two=[2,3]\n";
         out << "three=1,2,3\n";
     }
 
-    std::vector<int> two, three;
+    std::vector<int> zero1, zero2, one, two, three;
+    app.add_option("--zero1", zero1)->required()->expected(0, 99)->default_str("{}");
+    app.add_option("--zero2", zero2)->required()->expected(0, 99)->default_val(std::vector<int>{});
+    app.add_option("--one", one)->required();
     app.add_option("--two", two)->expected(2)->required();
     app.add_option("--three", three)->required();
 
     run();
 
+    CHECK(zero1 == std::vector<int>({}));
+    CHECK(zero2 == std::vector<int>({}));
+    CHECK(one == std::vector<int>({1}));
     CHECK(two == std::vector<int>({2, 3}));
     CHECK(three == std::vector<int>({1, 2, 3}));
 }
@@ -1096,9 +1128,9 @@ TEST_CASE_METHOD(TApp, "IniLayered", "[config]") {
 
     int one{0}, two{0}, three{0};
     app.add_option("--val", one);
-    auto subcom = app.add_subcommand("subcom");
+    auto *subcom = app.add_subcommand("subcom");
     subcom->add_option("--val", two);
-    auto subsubcom = subcom->add_subcommand("subsubcom");
+    auto *subsubcom = subcom->add_subcommand("subsubcom");
     subsubcom->add_option("--val", three);
 
     run();
@@ -1128,9 +1160,9 @@ TEST_CASE_METHOD(TApp, "IniLayeredStream", "[config]") {
 
     int one{0}, two{0}, three{0};
     app.add_option("--val", one);
-    auto subcom = app.add_subcommand("subcom");
+    auto *subcom = app.add_subcommand("subcom");
     subcom->add_option("--val", two);
-    auto subsubcom = subcom->add_subcommand("subsubcom");
+    auto *subsubcom = subcom->add_subcommand("subsubcom");
     subsubcom->add_option("--val", three);
 
     std::ifstream in{tmpini};
@@ -1162,9 +1194,9 @@ TEST_CASE_METHOD(TApp, "IniLayeredDotSection", "[config]") {
 
     int one{0}, two{0}, three{0};
     app.add_option("--val", one);
-    auto subcom = app.add_subcommand("subcom");
+    auto *subcom = app.add_subcommand("subcom");
     subcom->add_option("--val", two);
-    auto subsubcom = subcom->add_subcommand("subsubcom");
+    auto *subsubcom = subcom->add_subcommand("subsubcom");
     subsubcom->add_option("--val", three);
 
     run();
@@ -1201,9 +1233,9 @@ TEST_CASE_METHOD(TApp, "IniLayeredCustomSectionSeparator", "[config]") {
     app.get_config_formatter_base()->parentSeparator('|');
     int one{0}, two{0}, three{0};
     app.add_option("--val", one);
-    auto subcom = app.add_subcommand("subcom");
+    auto *subcom = app.add_subcommand("subcom");
     subcom->add_option("--val", two);
-    auto subsubcom = subcom->add_subcommand("subsubcom");
+    auto *subsubcom = subcom->add_subcommand("subsubcom");
     subsubcom->add_option("--val", three);
 
     run();
@@ -1216,6 +1248,30 @@ TEST_CASE_METHOD(TApp, "IniLayeredCustomSectionSeparator", "[config]") {
     CHECK(!*subcom);
 }
 
+TEST_CASE_METHOD(TApp, "IniLayeredOptionGroupAlias", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "val=1" << std::endl;
+        out << "[ogroup]" << std::endl;
+        out << "val2=2" << std::endl;
+    }
+    int one{0}, two{0};
+    app.add_option("--val", one);
+    auto *subcom = app.add_option_group("ogroup")->alias("ogroup");
+    subcom->add_option("--val2", two);
+
+    run();
+
+    CHECK(one == 1);
+    CHECK(two == 2);
+}
+
 TEST_CASE_METHOD(TApp, "IniSubcommandConfigurable", "[config]") {
 
     TempFile tmpini{"TestIniTmp.ini"};
@@ -1233,10 +1289,10 @@ TEST_CASE_METHOD(TApp, "IniSubcommandConfigurable", "[config]") {
 
     int one{0}, two{0}, three{0};
     app.add_option("--val", one);
-    auto subcom = app.add_subcommand("subcom");
+    auto *subcom = app.add_subcommand("subcom");
     subcom->configurable();
     subcom->add_option("--val", two);
-    auto subsubcom = subcom->add_subcommand("subsubcom");
+    auto *subsubcom = subcom->add_subcommand("subsubcom");
     subsubcom->add_option("--val", three);
 
     run();
@@ -1267,15 +1323,15 @@ TEST_CASE_METHOD(TApp, "IniSubcommandConfigurablePreParse", "[config]") {
 
     int one{0}, two{0}, three{0}, four{0};
     app.add_option("--val", one);
-    auto subcom = app.add_subcommand("subcom");
-    auto subcom2 = app.add_subcommand("subcom2");
+    auto *subcom = app.add_subcommand("subcom");
+    auto *subcom2 = app.add_subcommand("subcom2");
     subcom->configurable();
     std::vector<std::size_t> parse_c;
     subcom->preparse_callback([&parse_c](std::size_t cnt) { parse_c.push_back(cnt); });
     subcom->add_option("--val", two);
     subcom2->add_option("--val", four);
     subcom2->preparse_callback([&parse_c](std::size_t cnt) { parse_c.push_back(cnt + 2623); });
-    auto subsubcom = subcom->add_subcommand("subsubcom");
+    auto *subsubcom = subcom->add_subcommand("subsubcom");
     subsubcom->add_option("--val", three);
 
     run();
@@ -1350,7 +1406,7 @@ TEST_CASE_METHOD(TApp, "jsonLikeParsing", "[config]") {
         std::ofstream out{tmpjson};
         out << "{" << std::endl;
         out << "\"val\":1," << std::endl;
-        out << "\"val2\":\"test\"," << std::endl;
+        out << R"("val2":"test",)" << std::endl;
         out << "\"flag\":true" << std::endl;
         out << "}" << std::endl;
     }
@@ -1433,15 +1489,15 @@ TEST_CASE_METHOD(TApp, "IniSubcommandConfigurableParseComplete", "[config]") {
 
     int one{0}, two{0}, three{0}, four{0};
     app.add_option("--val", one);
-    auto subcom = app.add_subcommand("subcom");
-    auto subcom2 = app.add_subcommand("subcom2");
+    auto *subcom = app.add_subcommand("subcom");
+    auto *subcom2 = app.add_subcommand("subcom2");
     subcom->configurable();
     std::vector<std::size_t> parse_c;
     subcom->parse_complete_callback([&parse_c]() { parse_c.push_back(58); });
     subcom->add_option("--val", two);
     subcom2->add_option("--val", four);
     subcom2->parse_complete_callback([&parse_c]() { parse_c.push_back(2623); });
-    auto subsubcom = subcom->add_subcommand("subsubcom");
+    auto *subsubcom = subcom->add_subcommand("subsubcom");
     // configurable should be inherited
     subsubcom->parse_complete_callback([&parse_c]() { parse_c.push_back(68); });
     subsubcom->add_option("--val", three);
@@ -1480,8 +1536,8 @@ TEST_CASE_METHOD(TApp, "IniSubcommandMultipleSections", "[config]") {
 
     int one{0}, two{0}, three{0}, four{0};
     app.add_option("--val", one);
-    auto subcom = app.add_subcommand("subcom");
-    auto subcom2 = app.add_subcommand("subcom2");
+    auto *subcom = app.add_subcommand("subcom");
+    auto *subcom2 = app.add_subcommand("subcom2");
     subcom->configurable();
     std::vector<std::size_t> parse_c;
     subcom->parse_complete_callback([&parse_c]() { parse_c.push_back(58); });
@@ -1489,7 +1545,7 @@ TEST_CASE_METHOD(TApp, "IniSubcommandMultipleSections", "[config]") {
     subcom2->add_option("--val", four);
     subcom2->parse_complete_callback([&parse_c]() { parse_c.push_back(2623); });
     subcom2->configurable(false);
-    auto subsubcom = subcom->add_subcommand("subsubcom");
+    auto *subsubcom = subcom->add_subcommand("subsubcom");
     // configurable should be inherited
     subsubcom->parse_complete_callback([&parse_c]() { parse_c.push_back(68); });
     subsubcom->add_option("--val", three);
@@ -1521,7 +1577,7 @@ TEST_CASE_METHOD(TApp, "DuplicateSubcommandCallbacks", "[config]") {
         out << "[[foo]]" << std::endl;
     }
 
-    auto foo = app.add_subcommand("foo");
+    auto *foo = app.add_subcommand("foo");
     int count{0};
     foo->callback([&count]() { ++count; });
     foo->immediate_callback();
@@ -1532,6 +1588,25 @@ TEST_CASE_METHOD(TApp, "DuplicateSubcommandCallbacks", "[config]") {
     CHECK(3 == count);
 }
 
+TEST_CASE_METHOD(TApp, "SubcommandCallbackSingle", "[config]") {
+
+    TempFile tmptoml{"Testtomlcallback.toml"};
+
+    app.set_config("--config", tmptoml);
+
+    {
+        std::ofstream out{tmptoml};
+        out << "[foo]" << std::endl;
+    }
+    int count{0};
+    auto *foo = app.add_subcommand("foo");
+    foo->configurable();
+    foo->callback([&count]() { ++count; });
+
+    run();
+    CHECK(1 == count);
+}
+
 TEST_CASE_METHOD(TApp, "IniFailure", "[config]") {
 
     TempFile tmpini{"TestIniTmp.ini"};
@@ -1667,6 +1742,23 @@ TEST_CASE_METHOD(TApp, "IniFlagDual", "[config]") {
     CHECK_THROWS_AS(run(), CLI::ConversionError);
 }
 
+TEST_CASE_METHOD(TApp, "IniVectorMax", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    std::vector<std::string> v1;
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    app.add_option("--vec", v1)->expected(0, 2);
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "vec=[a,b,c]" << std::endl;
+    }
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
 TEST_CASE_METHOD(TApp, "IniShort", "[config]") {
 
     TempFile tmpini{"TestIniTmp.ini"};
@@ -1684,6 +1776,64 @@ TEST_CASE_METHOD(TApp, "IniShort", "[config]") {
     CHECK(3 == key);
 }
 
+TEST_CASE_METHOD(TApp, "IniDefaultPath", "[config]") {
+
+    TempFile tmpini{"../TestIniTmp.ini"};
+
+    int key{0};
+    app.add_option("--flag,-f", key);
+    app.set_config("--config", "TestIniTmp.ini")->transform(CLI::FileOnDefaultPath("../"));
+
+    {
+        std::ofstream out{tmpini};
+        out << "f=3" << std::endl;
+    }
+
+    REQUIRE_NOTHROW(run());
+    CHECK(3 == key);
+}
+
+TEST_CASE_METHOD(TApp, "IniMultipleDefaultPath", "[config]") {
+
+    TempFile tmpini{"../TestIniTmp.ini"};
+
+    int key{0};
+    app.add_option("--flag,-f", key);
+    auto *cfgOption = app.set_config("--config", "doesnotexist.ini")
+                          ->transform(CLI::FileOnDefaultPath("../"))
+                          ->transform(CLI::FileOnDefaultPath("../other", false));
+
+    {
+        std::ofstream out{tmpini};
+        out << "f=3" << std::endl;
+    }
+
+    args = {"--config", "TestIniTmp.ini"};
+    REQUIRE_NOTHROW(run());
+    CHECK(3 == key);
+    CHECK(cfgOption->as<std::string>() == "../TestIniTmp.ini");
+}
+
+TEST_CASE_METHOD(TApp, "IniMultipleDefaultPathAlternate", "[config]") {
+
+    TempFile tmpini{"../TestIniTmp.ini"};
+
+    int key{0};
+    app.add_option("--flag,-f", key);
+    auto *cfgOption = app.set_config("--config", "doesnotexist.ini")
+                          ->transform(CLI::FileOnDefaultPath("../other") | CLI::FileOnDefaultPath("../"));
+
+    {
+        std::ofstream out{tmpini};
+        out << "f=3" << std::endl;
+    }
+
+    args = {"--config", "TestIniTmp.ini"};
+    REQUIRE_NOTHROW(run());
+    CHECK(3 == key);
+    CHECK(cfgOption->as<std::string>() == "../TestIniTmp.ini");
+}
+
 TEST_CASE_METHOD(TApp, "IniPositional", "[config]") {
 
     TempFile tmpini{"TestIniTmp.ini"};
@@ -1874,6 +2024,51 @@ TEST_CASE_METHOD(TApp, "IniFalseFlagsDefDisableOverrideSuccess", "[config]") {
     CHECK(val == 15);
 }
 
+static const int fclear3 = fileClear("TestIniTmp3.ini");
+
+TEST_CASE_METHOD(TApp, "IniDisableFlagOverride", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+    TempFile tmpini2{"TestIniTmp2.ini"};
+    TempFile tmpini3{"TestIniTmp3.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=2" << std::endl;
+    }
+
+    {
+        std::ofstream out{tmpini2};
+        out << "[default]" << std::endl;
+        out << "two=7" << std::endl;
+    }
+
+    {
+        std::ofstream out{tmpini3};
+        out << "[default]" << std::endl;
+        out << "three=true" << std::endl;
+    }
+
+    int val{0};
+    app.add_flag("--one{1},--two{2},--three{3}", val)->disable_flag_override();
+
+    run();
+    CHECK(tmpini.c_str() == app["--config"]->as<std::string>());
+    CHECK(val == 2);
+
+    args = {"--config", tmpini2};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+
+    args = {"--config", tmpini3};
+    run();
+
+    CHECK(val == 3);
+    CHECK(tmpini3.c_str() == app.get_config_ptr()->as<std::string>());
+}
+
 TEST_CASE_METHOD(TApp, "TomlOutputSimple", "[config]") {
 
     int v{0};
@@ -2035,7 +2230,7 @@ TEST_CASE_METHOD(TApp, "TomlOutputMultiLineDescription", "[config]") {
 TEST_CASE_METHOD(TApp, "TomlOutputOptionGroupMultiLineDescription", "[config]") {
     std::string flag = "flag";
     const std::string description = "Short flag description.\n";
-    auto og = app.add_option_group("group");
+    auto *og = app.add_option_group("group");
     og->description("Option group description.\n"
                     "That has multiple lines.");
     og->add_flag("--" + flag, description);
@@ -2049,7 +2244,7 @@ TEST_CASE_METHOD(TApp, "TomlOutputOptionGroupMultiLineDescription", "[config]")
 TEST_CASE_METHOD(TApp, "TomlOutputSubcommandMultiLineDescription", "[config]") {
     std::string flag = "flag";
     const std::string description = "Short flag description.\n";
-    auto subcom = app.add_subcommand("subcommand");
+    auto *subcom = app.add_subcommand("subcommand");
     subcom->configurable();
     subcom->description("Subcommand description.\n"
                         "That has multiple lines.");
@@ -2069,7 +2264,7 @@ TEST_CASE_METHOD(TApp, "TomlOutputOptionGroup", "[config]") {
     const std::string description2 = "Second description.";
     app.add_flag("--" + flag1, description1)->group("group1");
     app.add_flag("--" + flag2, description2)->group("group2");
-    auto og = app.add_option_group("group3", "g3 desc");
+    auto *og = app.add_option_group("group3", "g3 desc");
     og->add_option("--dval", val)->capture_default_str()->group("");
 
     run();
@@ -2103,6 +2298,19 @@ TEST_CASE_METHOD(TApp, "TomlOutputVector", "[config]") {
     CHECK(str == "vector=[1, 2, 3]\n");
 }
 
+TEST_CASE_METHOD(TApp, "TomlOutputTuple", "[config]") {
+
+    std::tuple<double, double, double, double> t;
+    app.add_option("--tuple", t);
+    app.config_formatter(std::make_shared<CLI::ConfigTOML>());
+    args = {"--tuple", "1", "2", "3", "4"};
+
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK(str == "tuple=[1, 2, 3, 4]\n");
+}
+
 TEST_CASE_METHOD(TApp, "ConfigOutputVectorCustom", "[config]") {
 
     std::vector<int> v;
@@ -2134,7 +2342,7 @@ TEST_CASE_METHOD(TApp, "TomlOutputFlag", "[config]") {
     CHECK_THAT(str, Contains("simple=3"));
     CHECK_THAT(str, !Contains("nothing"));
     CHECK_THAT(str, Contains("onething=true"));
-    CHECK_THAT(str, Contains("something=[true, true]"));
+    CHECK_THAT(str, Contains("something=2"));
 
     str = app.config_to_str(true);
     CHECK_THAT(str, Contains("nothing"));
@@ -2170,7 +2378,7 @@ TEST_CASE_METHOD(TApp, "TomlOutputDefault", "[config]") {
 TEST_CASE_METHOD(TApp, "TomlOutputSubcom", "[config]") {
 
     app.add_flag("--simple");
-    auto subcom = app.add_subcommand("other");
+    auto *subcom = app.add_subcommand("other");
     subcom->add_flag("--newer");
 
     args = {"--simple", "other", "--newer"};
@@ -2184,7 +2392,7 @@ TEST_CASE_METHOD(TApp, "TomlOutputSubcom", "[config]") {
 TEST_CASE_METHOD(TApp, "TomlOutputSubcomConfigurable", "[config]") {
 
     app.add_flag("--simple");
-    auto subcom = app.add_subcommand("other")->configurable();
+    auto *subcom = app.add_subcommand("other")->configurable();
     subcom->add_flag("--newer");
 
     args = {"--simple", "other", "--newer"};
@@ -2200,9 +2408,9 @@ TEST_CASE_METHOD(TApp, "TomlOutputSubcomConfigurable", "[config]") {
 TEST_CASE_METHOD(TApp, "TomlOutputSubsubcom", "[config]") {
 
     app.add_flag("--simple");
-    auto subcom = app.add_subcommand("other");
+    auto *subcom = app.add_subcommand("other");
     subcom->add_flag("--newer");
-    auto subsubcom = subcom->add_subcommand("sub2");
+    auto *subsubcom = subcom->add_subcommand("sub2");
     subsubcom->add_flag("--newest");
 
     args = {"--simple", "other", "--newer", "sub2", "--newest"};
@@ -2217,10 +2425,10 @@ TEST_CASE_METHOD(TApp, "TomlOutputSubsubcom", "[config]") {
 TEST_CASE_METHOD(TApp, "TomlOutputSubsubcomConfigurable", "[config]") {
 
     app.add_flag("--simple");
-    auto subcom = app.add_subcommand("other")->configurable();
+    auto *subcom = app.add_subcommand("other")->configurable();
     subcom->add_flag("--newer");
 
-    auto subsubcom = subcom->add_subcommand("sub2");
+    auto *subsubcom = subcom->add_subcommand("sub2");
     subsubcom->add_flag("--newest");
 
     args = {"--simple", "other", "--newer", "sub2", "--newest"};
@@ -2238,10 +2446,10 @@ TEST_CASE_METHOD(TApp, "TomlOutputSubsubcomConfigurable", "[config]") {
 TEST_CASE_METHOD(TApp, "TomlOutputSubcomNonConfigurable", "[config]") {
 
     app.add_flag("--simple");
-    auto subcom = app.add_subcommand("other", "other_descriptor")->configurable();
+    auto *subcom = app.add_subcommand("other", "other_descriptor")->configurable();
     subcom->add_flag("--newer");
 
-    auto subcom2 = app.add_subcommand("sub2", "descriptor2");
+    auto *subcom2 = app.add_subcommand("sub2", "descriptor2");
     subcom2->add_flag("--newest")->configurable(false);
 
     args = {"--simple", "other", "--newer", "sub2", "--newest"};
@@ -2259,14 +2467,14 @@ TEST_CASE_METHOD(TApp, "TomlOutputSubcomNonConfigurable", "[config]") {
 TEST_CASE_METHOD(TApp, "TomlOutputSubsubcomConfigurableDeep", "[config]") {
 
     app.add_flag("--simple");
-    auto subcom = app.add_subcommand("other")->configurable();
+    auto *subcom = app.add_subcommand("other")->configurable();
     subcom->add_flag("--newer");
 
-    auto subsubcom = subcom->add_subcommand("sub2");
+    auto *subsubcom = subcom->add_subcommand("sub2");
     subsubcom->add_flag("--newest");
-    auto sssscom = subsubcom->add_subcommand("sub-level2");
+    auto *sssscom = subsubcom->add_subcommand("sub-level2");
     subsubcom->add_flag("--still_newer");
-    auto s5com = sssscom->add_subcommand("sub-level3");
+    auto *s5com = sssscom->add_subcommand("sub-level3");
     s5com->add_flag("--absolute_newest");
 
     args = {"--simple", "other", "sub2", "sub-level2", "sub-level3", "--absolute_newest"};
@@ -2320,7 +2528,7 @@ TEST_CASE_METHOD(TApp, "StopReadingConfigOnClear", "[config]") {
     TempFile tmpini{"TestIniTmp.ini"};
 
     app.set_config("--config", tmpini);
-    auto ptr = app.set_config();  // Should *not* read config file
+    auto *ptr = app.set_config();  // Should *not* read config file
     CHECK(nullptr == ptr);
 
     {
@@ -2358,6 +2566,29 @@ TEST_CASE_METHOD(TApp, "ConfigWriteReadWrite", "[config]") {
     CHECK(config2 == config1);
 }
 
+TEST_CASE_METHOD(TApp, "ConfigWriteReadNegated", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+    bool flag{true};
+    app.add_flag("!--no-flag", flag);
+    args = {"--no-flag"};
+    run();
+
+    // Save config, with default values too
+    std::string config1 = app.config_to_str(false, false);
+    {
+        std::ofstream out{tmpini};
+        out << config1 << std::endl;
+    }
+    CHECK_FALSE(flag);
+    args.clear();
+    flag = true;
+    app.set_config("--config", tmpini, "Read an ini file", true);
+    run();
+
+    CHECK_FALSE(flag);
+}
+
 /////// INI output tests
 
 TEST_CASE_METHOD(TApp, "IniOutputSimple", "[config]") {
@@ -2479,7 +2710,7 @@ TEST_CASE_METHOD(TApp, "IniOutputMultiLineDescription", "[config]") {
 TEST_CASE_METHOD(TApp, "IniOutputOptionGroupMultiLineDescription", "[config]") {
     std::string flag = "flag";
     const std::string description = "Short flag description.\n";
-    auto og = app.add_option_group("group");
+    auto *og = app.add_option_group("group");
     og->description("Option group description.\n"
                     "That has multiple lines.");
     og->add_flag("--" + flag, description);
@@ -2494,7 +2725,7 @@ TEST_CASE_METHOD(TApp, "IniOutputOptionGroupMultiLineDescription", "[config]") {
 TEST_CASE_METHOD(TApp, "IniOutputSubcommandMultiLineDescription", "[config]") {
     std::string flag = "flag";
     const std::string description = "Short flag description.\n";
-    auto subcom = app.add_subcommand("subcommand");
+    auto *subcom = app.add_subcommand("subcommand");
     subcom->configurable();
     subcom->description("Subcommand description.\n"
                         "That has multiple lines.");
@@ -2515,7 +2746,7 @@ TEST_CASE_METHOD(TApp, "IniOutputOptionGroup", "[config]") {
     const std::string description2 = "Second description.";
     app.add_flag("--" + flag1, description1)->group("group1");
     app.add_flag("--" + flag2, description2)->group("group2");
-    auto og = app.add_option_group("group3", "g3 desc");
+    auto *og = app.add_option_group("group3", "g3 desc");
     og->add_option("--dval", val)->capture_default_str()->group("");
     app.config_formatter(std::make_shared<CLI::ConfigINI>());
     run();
@@ -2565,7 +2796,7 @@ TEST_CASE_METHOD(TApp, "IniOutputFlag", "[config]") {
     CHECK_THAT(str, Contains("simple=3"));
     CHECK_THAT(str, !Contains("nothing"));
     CHECK_THAT(str, Contains("onething=true"));
-    CHECK_THAT(str, Contains("something=true true"));
+    CHECK_THAT(str, Contains("something=2"));
 
     str = app.config_to_str(true);
     CHECK_THAT(str, Contains("nothing"));
@@ -2601,7 +2832,7 @@ TEST_CASE_METHOD(TApp, "IniOutputDefault", "[config]") {
 TEST_CASE_METHOD(TApp, "IniOutputSubcom", "[config]") {
 
     app.add_flag("--simple");
-    auto subcom = app.add_subcommand("other");
+    auto *subcom = app.add_subcommand("other");
     subcom->add_flag("--newer");
     app.config_formatter(std::make_shared<CLI::ConfigINI>());
     args = {"--simple", "other", "--newer"};
@@ -2615,7 +2846,7 @@ TEST_CASE_METHOD(TApp, "IniOutputSubcom", "[config]") {
 TEST_CASE_METHOD(TApp, "IniOutputSubcomCustomSep", "[config]") {
 
     app.add_flag("--simple");
-    auto subcom = app.add_subcommand("other");
+    auto *subcom = app.add_subcommand("other");
     subcom->add_flag("--newer");
     app.config_formatter(std::make_shared<CLI::ConfigINI>());
     app.get_config_formatter_base()->parentSeparator(':');
@@ -2630,7 +2861,7 @@ TEST_CASE_METHOD(TApp, "IniOutputSubcomCustomSep", "[config]") {
 TEST_CASE_METHOD(TApp, "IniOutputSubcomConfigurable", "[config]") {
 
     app.add_flag("--simple");
-    auto subcom = app.add_subcommand("other")->configurable();
+    auto *subcom = app.add_subcommand("other")->configurable();
     subcom->add_flag("--newer");
     app.config_formatter(std::make_shared<CLI::ConfigINI>());
     args = {"--simple", "other", "--newer"};
@@ -2646,9 +2877,9 @@ TEST_CASE_METHOD(TApp, "IniOutputSubcomConfigurable", "[config]") {
 TEST_CASE_METHOD(TApp, "IniOutputSubsubcom", "[config]") {
 
     app.add_flag("--simple");
-    auto subcom = app.add_subcommand("other");
+    auto *subcom = app.add_subcommand("other");
     subcom->add_flag("--newer");
-    auto subsubcom = subcom->add_subcommand("sub2");
+    auto *subsubcom = subcom->add_subcommand("sub2");
     subsubcom->add_flag("--newest");
     app.config_formatter(std::make_shared<CLI::ConfigINI>());
     args = {"--simple", "other", "--newer", "sub2", "--newest"};
@@ -2663,9 +2894,9 @@ TEST_CASE_METHOD(TApp, "IniOutputSubsubcom", "[config]") {
 TEST_CASE_METHOD(TApp, "IniOutputSubsubcomCustomSep", "[config]") {
 
     app.add_flag("--simple");
-    auto subcom = app.add_subcommand("other");
+    auto *subcom = app.add_subcommand("other");
     subcom->add_flag("--newer");
-    auto subsubcom = subcom->add_subcommand("sub2");
+    auto *subsubcom = subcom->add_subcommand("sub2");
     subsubcom->add_flag("--newest");
     app.config_formatter(std::make_shared<CLI::ConfigINI>());
     app.get_config_formatter_base()->parentSeparator('|');
@@ -2681,10 +2912,10 @@ TEST_CASE_METHOD(TApp, "IniOutputSubsubcomCustomSep", "[config]") {
 TEST_CASE_METHOD(TApp, "IniOutputSubsubcomConfigurable", "[config]") {
 
     app.add_flag("--simple");
-    auto subcom = app.add_subcommand("other")->configurable();
+    auto *subcom = app.add_subcommand("other")->configurable();
     subcom->add_flag("--newer");
 
-    auto subsubcom = subcom->add_subcommand("sub2");
+    auto *subsubcom = subcom->add_subcommand("sub2");
     subsubcom->add_flag("--newest");
     app.config_formatter(std::make_shared<CLI::ConfigINI>());
     args = {"--simple", "other", "--newer", "sub2", "--newest"};
@@ -2702,14 +2933,14 @@ TEST_CASE_METHOD(TApp, "IniOutputSubsubcomConfigurable", "[config]") {
 TEST_CASE_METHOD(TApp, "IniOutputSubsubcomConfigurableDeep", "[config]") {
 
     app.add_flag("--simple");
-    auto subcom = app.add_subcommand("other")->configurable();
+    auto *subcom = app.add_subcommand("other")->configurable();
     subcom->add_flag("--newer");
 
-    auto subsubcom = subcom->add_subcommand("sub2");
+    auto *subsubcom = subcom->add_subcommand("sub2");
     subsubcom->add_flag("--newest");
-    auto sssscom = subsubcom->add_subcommand("sub-level2");
+    auto *sssscom = subsubcom->add_subcommand("sub-level2");
     subsubcom->add_flag("--still_newer");
-    auto s5com = sssscom->add_subcommand("sub-level3");
+    auto *s5com = sssscom->add_subcommand("sub-level3");
     s5com->add_flag("--absolute_newest");
     app.config_formatter(std::make_shared<CLI::ConfigINI>());
     args = {"--simple", "other", "sub2", "sub-level2", "sub-level3", "--absolute_newest"};
diff --git a/tests/CreationTest.cpp b/tests/CreationTest.cpp
index 4790057..eaf3688 100644
--- a/tests/CreationTest.cpp
+++ b/tests/CreationTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -41,7 +41,7 @@ TEST_CASE_METHOD(TApp, "AddingExistingWithCase", "[creation]") {
 }
 
 TEST_CASE_METHOD(TApp, "AddingExistingWithCaseAfter", "[creation]") {
-    auto count = app.add_flag("-c,--count");
+    auto *count = app.add_flag("-c,--count");
     app.add_flag("--Cat,-C");
 
     CHECK_THROWS_AS(count->ignore_case(), CLI::OptionAlreadyAdded);
@@ -49,20 +49,20 @@ TEST_CASE_METHOD(TApp, "AddingExistingWithCaseAfter", "[creation]") {
 
 TEST_CASE_METHOD(TApp, "AddingExistingWithCaseAfter2", "[creation]") {
     app.add_flag("-c,--count");
-    auto cat = app.add_flag("--Cat,-C");
+    auto *cat = app.add_flag("--Cat,-C");
 
     CHECK_THROWS_AS(cat->ignore_case(), CLI::OptionAlreadyAdded);
 }
 
 TEST_CASE_METHOD(TApp, "AddingExistingWithUnderscoreAfter", "[creation]") {
-    auto count = app.add_flag("--underscore");
+    auto *count = app.add_flag("--underscore");
     app.add_flag("--under_score");
 
     CHECK_THROWS_AS(count->ignore_underscore(), CLI::OptionAlreadyAdded);
 }
 
 TEST_CASE_METHOD(TApp, "AddingExistingWithUnderscoreAfter2", "[creation]") {
-    auto count = app.add_flag("--under_score");
+    auto *count = app.add_flag("--under_score");
     app.add_flag("--underscore");
 
     CHECK_THROWS_AS(count->ignore_underscore(), CLI::OptionAlreadyAdded);
@@ -125,13 +125,13 @@ TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithUnderscoreFirst", "[creation]"
 
 TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithCaseInplace", "[creation]") {
     app.add_subcommand("first");
-    auto first = app.add_subcommand("fIrst");
+    auto *first = app.add_subcommand("fIrst");
 
     CHECK_THROWS_AS(first->ignore_case(), CLI::OptionAlreadyAdded);
 }
 
 TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithCaseInplace2", "[creation]") {
-    auto first = app.add_subcommand("first");
+    auto *first = app.add_subcommand("first");
     app.add_subcommand("fIrst");
 
     CHECK_THROWS_AS(first->ignore_case(), CLI::OptionAlreadyAdded);
@@ -139,29 +139,29 @@ TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithCaseInplace2", "[creation]") {
 
 TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithUnderscoreInplace", "[creation]") {
     app.add_subcommand("first_option");
-    auto first = app.add_subcommand("firstoption");
+    auto *first = app.add_subcommand("firstoption");
 
     CHECK_THROWS_AS(first->ignore_underscore(), CLI::OptionAlreadyAdded);
 }
 
 TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithUnderscoreInplace2", "[creation]") {
-    auto first = app.add_subcommand("firstoption");
+    auto *first = app.add_subcommand("firstoption");
     app.add_subcommand("first_option");
 
     CHECK_THROWS_AS(first->ignore_underscore(), CLI::OptionAlreadyAdded);
 }
 
 TEST_CASE_METHOD(TApp, "MultipleSubcomNoMatchingInplace2", "[creation]") {
-    auto first = app.add_subcommand("first");
-    auto second = app.add_subcommand("second");
+    auto *first = app.add_subcommand("first");
+    auto *second = app.add_subcommand("second");
 
     CHECK_NOTHROW(first->ignore_case());
     CHECK_NOTHROW(second->ignore_case());
 }
 
 TEST_CASE_METHOD(TApp, "MultipleSubcomNoMatchingInplaceUnderscore2", "[creation]") {
-    auto first = app.add_subcommand("first_option");
-    auto second = app.add_subcommand("second_option");
+    auto *first = app.add_subcommand("first_option");
+    auto *second = app.add_subcommand("second_option");
 
     CHECK_NOTHROW(first->ignore_underscore());
     CHECK_NOTHROW(second->ignore_underscore());
@@ -183,25 +183,25 @@ TEST_CASE_METHOD(TApp, "IncorrectConstructionFlagPositional3", "[creation]") {
 }
 
 TEST_CASE_METHOD(TApp, "IncorrectConstructionNeedsCannotFind", "[creation]") {
-    auto cat = app.add_flag("--cat");
+    auto *cat = app.add_flag("--cat");
     CHECK_THROWS_AS(cat->needs("--nothing"), CLI::IncorrectConstruction);
 }
 
 TEST_CASE_METHOD(TApp, "IncorrectConstructionExcludesCannotFind", "[creation]") {
-    auto cat = app.add_flag("--cat");
+    auto *cat = app.add_flag("--cat");
     CHECK_THROWS_AS(cat->excludes("--nothing"), CLI::IncorrectConstruction);
 }
 
 TEST_CASE_METHOD(TApp, "IncorrectConstructionDuplicateNeeds", "[creation]") {
-    auto cat = app.add_flag("--cat");
-    auto other = app.add_flag("--other");
+    auto *cat = app.add_flag("--cat");
+    auto *other = app.add_flag("--other");
     REQUIRE_NOTHROW(cat->needs(other));
     // duplicated needs is redundant but not an error
     CHECK_NOTHROW(cat->needs(other));
 }
 
 TEST_CASE_METHOD(TApp, "IncorrectConstructionDuplicateNeedsTxt", "[creation]") {
-    auto cat = app.add_flag("--cat");
+    auto *cat = app.add_flag("--cat");
     app.add_flag("--other");
     REQUIRE_NOTHROW(cat->needs("--other"));
     // duplicate needs is redundant but not an error
@@ -210,28 +210,28 @@ TEST_CASE_METHOD(TApp, "IncorrectConstructionDuplicateNeedsTxt", "[creation]") {
 
 // Now allowed
 TEST_CASE_METHOD(TApp, "CorrectConstructionDuplicateExcludes", "[creation]") {
-    auto cat = app.add_flag("--cat");
-    auto other = app.add_flag("--other");
+    auto *cat = app.add_flag("--cat");
+    auto *other = app.add_flag("--other");
     REQUIRE_NOTHROW(cat->excludes(other));
     REQUIRE_NOTHROW(other->excludes(cat));
 }
 
 // Now allowed
 TEST_CASE_METHOD(TApp, "CorrectConstructionDuplicateExcludesTxt", "[creation]") {
-    auto cat = app.add_flag("--cat");
-    auto other = app.add_flag("--other");
+    auto *cat = app.add_flag("--cat");
+    auto *other = app.add_flag("--other");
     REQUIRE_NOTHROW(cat->excludes("--other"));
     REQUIRE_NOTHROW(other->excludes("--cat"));
 }
 
 TEST_CASE_METHOD(TApp, "CheckName", "[creation]") {
-    auto long1 = app.add_flag("--long1");
-    auto long2 = app.add_flag("--Long2");
-    auto short1 = app.add_flag("-a");
-    auto short2 = app.add_flag("-B");
+    auto *long1 = app.add_flag("--long1");
+    auto *long2 = app.add_flag("--Long2");
+    auto *short1 = app.add_flag("-a");
+    auto *short2 = app.add_flag("-B");
     int x{0}, y{0};
-    auto pos1 = app.add_option("pos1", x);
-    auto pos2 = app.add_option("pOs2", y);
+    auto *pos1 = app.add_option("pos1", x);
+    auto *pos2 = app.add_option("pOs2", y);
 
     CHECK(long1->check_name("--long1"));
     CHECK(!long1->check_name("--lonG1"));
@@ -253,13 +253,13 @@ TEST_CASE_METHOD(TApp, "CheckName", "[creation]") {
 }
 
 TEST_CASE_METHOD(TApp, "CheckNameNoCase", "[creation]") {
-    auto long1 = app.add_flag("--long1")->ignore_case();
-    auto long2 = app.add_flag("--Long2")->ignore_case();
-    auto short1 = app.add_flag("-a")->ignore_case();
-    auto short2 = app.add_flag("-B")->ignore_case();
+    auto *long1 = app.add_flag("--long1")->ignore_case();
+    auto *long2 = app.add_flag("--Long2")->ignore_case();
+    auto *short1 = app.add_flag("-a")->ignore_case();
+    auto *short2 = app.add_flag("-B")->ignore_case();
     int x{0}, y{0};
-    auto pos1 = app.add_option("pos1", x)->ignore_case();
-    auto pos2 = app.add_option("pOs2", y)->ignore_case();
+    auto *pos1 = app.add_option("pos1", x)->ignore_case();
+    auto *pos2 = app.add_option("pOs2", y)->ignore_case();
 
     CHECK(long1->check_name("--long1"));
     CHECK(long1->check_name("--lonG1"));
@@ -281,12 +281,12 @@ TEST_CASE_METHOD(TApp, "CheckNameNoCase", "[creation]") {
 }
 
 TEST_CASE_METHOD(TApp, "CheckNameNoUnderscore", "[creation]") {
-    auto long1 = app.add_flag("--longoption1")->ignore_underscore();
-    auto long2 = app.add_flag("--long_option2")->ignore_underscore();
+    auto *long1 = app.add_flag("--longoption1")->ignore_underscore();
+    auto *long2 = app.add_flag("--long_option2")->ignore_underscore();
 
     int x{0}, y{0};
-    auto pos1 = app.add_option("pos_option_1", x)->ignore_underscore();
-    auto pos2 = app.add_option("posoption2", y)->ignore_underscore();
+    auto *pos1 = app.add_option("pos_option_1", x)->ignore_underscore();
+    auto *pos2 = app.add_option("posoption2", y)->ignore_underscore();
 
     CHECK(long1->check_name("--long_option1"));
     CHECK(long1->check_name("--longoption_1"));
@@ -312,12 +312,12 @@ TEST_CASE_METHOD(TApp, "CheckNameNoUnderscore", "[creation]") {
 }
 
 TEST_CASE_METHOD(TApp, "CheckNameNoCaseNoUnderscore", "[creation]") {
-    auto long1 = app.add_flag("--LongoptioN1")->ignore_underscore()->ignore_case();
-    auto long2 = app.add_flag("--long_Option2")->ignore_case()->ignore_underscore();
+    auto *long1 = app.add_flag("--LongoptioN1")->ignore_underscore()->ignore_case();
+    auto *long2 = app.add_flag("--long_Option2")->ignore_case()->ignore_underscore();
 
     int x{0}, y{0};
-    auto pos1 = app.add_option("pos_Option_1", x)->ignore_underscore()->ignore_case();
-    auto pos2 = app.add_option("posOption2", y)->ignore_case()->ignore_underscore();
+    auto *pos1 = app.add_option("pos_Option_1", x)->ignore_underscore()->ignore_case();
+    auto *pos2 = app.add_option("posOption2", y)->ignore_case()->ignore_underscore();
 
     CHECK(long1->check_name("--Long_Option1"));
     CHECK(long1->check_name("--lONgoption_1"));
@@ -344,7 +344,7 @@ TEST_CASE_METHOD(TApp, "CheckNameNoCaseNoUnderscore", "[creation]") {
 
 TEST_CASE_METHOD(TApp, "PreSpaces", "[creation]") {
     int x{0};
-    auto myapp = app.add_option(" -a, --long, other", x);
+    auto *myapp = app.add_option(" -a, --long, other", x);
 
     CHECK(myapp->check_lname("long"));
     CHECK(myapp->check_sname("a"));
@@ -353,7 +353,7 @@ TEST_CASE_METHOD(TApp, "PreSpaces", "[creation]") {
 
 TEST_CASE_METHOD(TApp, "AllSpaces", "[creation]") {
     int x{0};
-    auto myapp = app.add_option(" -a , --long , other ", x);
+    auto *myapp = app.add_option(" -a , --long , other ", x);
 
     CHECK(myapp->check_lname("long"));
     CHECK(myapp->check_sname("a"));
@@ -365,26 +365,26 @@ TEST_CASE_METHOD(TApp, "OptionFromDefaults", "[creation]") {
 
     // Options should remember defaults
     int x{0};
-    auto opt = app.add_option("--simple", x);
+    auto *opt = app.add_option("--simple", x);
     CHECK(opt->get_required());
 
     // Flags cannot be required
-    auto flag = app.add_flag("--other");
+    auto *flag = app.add_flag("--other");
     CHECK(!flag->get_required());
 
     app.option_defaults()->required(false);
-    auto opt2 = app.add_option("--simple2", x);
+    auto *opt2 = app.add_option("--simple2", x);
     CHECK(!opt2->get_required());
 
     app.option_defaults()->required()->ignore_case();
 
-    auto opt3 = app.add_option("--simple3", x);
+    auto *opt3 = app.add_option("--simple3", x);
     CHECK(opt3->get_required());
     CHECK(opt3->get_ignore_case());
 
     app.option_defaults()->required()->ignore_underscore();
 
-    auto opt4 = app.add_option("--simple4", x);
+    auto *opt4 = app.add_option("--simple4", x);
     CHECK(opt4->get_required());
     CHECK(opt4->get_ignore_underscore());
 }
@@ -408,7 +408,7 @@ TEST_CASE_METHOD(TApp, "OptionFromDefaultsSubcommands", "[creation]") {
         ->disable_flag_override()
         ->group("Something");
 
-    auto app2 = app.add_subcommand("app2");
+    auto *app2 = app.add_subcommand("app2");
 
     CHECK(app2->option_defaults()->get_required());
     CHECK(CLI::MultiOptionPolicy::TakeLast == app2->option_defaults()->get_multi_option_policy());
@@ -421,10 +421,10 @@ TEST_CASE_METHOD(TApp, "OptionFromDefaultsSubcommands", "[creation]") {
 
 TEST_CASE_METHOD(TApp, "GetNameCheck", "[creation]") {
     int x{0};
-    auto a = app.add_flag("--that");
-    auto b = app.add_flag("-x");
-    auto c = app.add_option("pos", x);
-    auto d = app.add_option("one,-o,--other", x);
+    auto *a = app.add_flag("--that");
+    auto *b = app.add_flag("-x");
+    auto *c = app.add_option("pos", x);
+    auto *d = app.add_option("one,-o,--other", x);
 
     CHECK("--that" == a->get_name(false, true));
     CHECK("-x" == b->get_name(false, true));
@@ -455,7 +455,7 @@ TEST_CASE_METHOD(TApp, "SubcommandDefaults", "[creation]") {
     CHECK(!app.get_configurable());
     CHECK(!app.get_validate_positionals());
 
-    CHECK("" == app.get_footer());
+    CHECK(app.get_footer().empty());
     CHECK("Subcommands" == app.get_group());
     CHECK(0u == app.get_require_subcommand_min());
     CHECK(0u == app.get_require_subcommand_max());
@@ -478,7 +478,7 @@ TEST_CASE_METHOD(TApp, "SubcommandDefaults", "[creation]") {
     app.group("Stuff");
     app.require_subcommand(2, 3);
 
-    auto app2 = app.add_subcommand("app2");
+    auto *app2 = app.add_subcommand("app2");
 
     // Initial defaults
     CHECK(app2->get_allow_extras());
@@ -533,8 +533,8 @@ TEST_CASE_METHOD(TApp, "SubcommandMinMax", "[creation]") {
 
 TEST_CASE_METHOD(TApp, "GetOptionList", "[creation]") {
     int two{0};
-    auto flag = app.add_flag("--one");
-    auto opt = app.add_option("--two", two);
+    auto *flag = app.add_flag("--one");
+    auto *opt = app.add_option("--two", two);
 
     const CLI::App &const_app = app;  // const alias to force use of const-methods
     std::vector<const CLI::Option *> opt_list = const_app.get_options();
@@ -549,6 +549,25 @@ TEST_CASE_METHOD(TApp, "GetOptionList", "[creation]") {
     }
 }
 
+TEST_CASE_METHOD(TApp, "GetOptionListFilter", "[creation]") {
+    int two{0};
+    auto *flag = app.add_flag("--one");
+    app.add_option("--two", two);
+
+    const CLI::App &const_app = app;  // const alias to force use of const-methods
+    std::vector<const CLI::Option *> opt_listc =
+        const_app.get_options([](const CLI::Option *opt) { return opt->get_name() == "--one"; });
+
+    REQUIRE(static_cast<std::size_t>(1) == opt_listc.size());
+    CHECK(flag == opt_listc.at(0));
+
+    std::vector<CLI::Option *> opt_list =
+        app.get_options([](const CLI::Option *opt) { return opt->get_name() == "--one"; });
+
+    REQUIRE(static_cast<std::size_t>(1) == opt_list.size());
+    CHECK(flag == opt_list.at(0));
+}
+
 TEST_CASE("ValidatorTests: TestValidatorCreation", "[creation]") {
     std::function<std::string(std::string &)> op1 = [](std::string &val) {
         return (val.size() >= 5) ? std::string{} : val;
@@ -561,9 +580,9 @@ TEST_CASE("ValidatorTests: TestValidatorCreation", "[creation]") {
     CHECK(V.get_active());
 
     CHECK("test" == V("test"));
-    CHECK(std::string{} == V("test5"));
+    CHECK(V("test5").empty());
 
-    CHECK(std::string{} == V.get_description());
+    CHECK(V.get_description().empty());
     V.description("this is a description");
     CHECK("this is a description" == V.get_description());
 }
@@ -717,7 +736,7 @@ class Unstreamable {
 
   public:
     Unstreamable() = default;
-    int get_x() const { return x_; }
+    CLI11_NODISCARD int get_x() const { return x_; }
     void set_x(int x) { x_ = x; }
 };
 
@@ -725,7 +744,7 @@ class Unstreamable {
 static_assert(!CLI::detail::is_istreamable<Unstreamable, std::istream>::value, "Unstreamable type is streamable");
 
 std::istream &operator>>(std::istream &in, Unstreamable &value) {
-    int x;
+    int x = 0;
     in >> x;
     value.set_x(x);
     return in;
diff --git a/tests/DeprecatedTest.cpp b/tests/DeprecatedTest.cpp
index f763a89..2a82335 100644
--- a/tests/DeprecatedTest.cpp
+++ b/tests/DeprecatedTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/tests/FormatterTest.cpp b/tests/FormatterTest.cpp
index 1516945..641fd40 100644
--- a/tests/FormatterTest.cpp
+++ b/tests/FormatterTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -63,7 +63,7 @@ TEST_CASE("Formatter: OptCustomize", "[formatter]") {
                   "Usage: [OPTIONS]\n\n"
                   "Options:\n"
                   "  -h,--help              Print this help message and exit\n"
-                  "  --opt INT (MUST HAVE)  Something\n\n");
+                  "  --opt INT (MUST HAVE)  Something\n");
 }
 
 TEST_CASE("Formatter: OptCustomizeSimple", "[formatter]") {
@@ -82,7 +82,7 @@ TEST_CASE("Formatter: OptCustomizeSimple", "[formatter]") {
                   "Usage: [OPTIONS]\n\n"
                   "Options:\n"
                   "  -h,--help              Print this help message and exit\n"
-                  "  --opt INT (MUST HAVE)  Something\n\n");
+                  "  --opt INT (MUST HAVE)  Something\n");
 }
 
 TEST_CASE("Formatter: OptCustomizeOptionText", "[formatter]") {
@@ -100,7 +100,7 @@ TEST_CASE("Formatter: OptCustomizeOptionText", "[formatter]") {
                   "Usage: [OPTIONS]\n\n"
                   "Options:\n"
                   "  -h,--help              Print this help message and exit\n"
-                  "  --opt (ARG)            Something\n\n");
+                  "  --opt (ARG)            Something\n");
 }
 
 TEST_CASE("Formatter: FalseFlagExample", "[formatter]") {
@@ -140,7 +140,7 @@ TEST_CASE("Formatter: AppCustomize", "[formatter]") {
                   "  -h,--help         Print this help message and exit\n\n"
                   "Subcommands:\n"
                   "  subcom1           This\n"
-                  "  subcom2           This\n\n");
+                  "  subcom2           This\n");
 }
 
 TEST_CASE("Formatter: AppCustomizeSimple", "[formatter]") {
@@ -159,7 +159,7 @@ TEST_CASE("Formatter: AppCustomizeSimple", "[formatter]") {
                   "  -h,--help         Print this help message and exit\n\n"
                   "Subcommands:\n"
                   "  subcom1           This\n"
-                  "  subcom2           This\n\n");
+                  "  subcom2           This\n");
 }
 
 TEST_CASE("Formatter: AllSub", "[formatter]") {
diff --git a/tests/HelpTest.cpp b/tests/HelpTest.cpp
index cb8c81a..996e12c 100644
--- a/tests/HelpTest.cpp
+++ b/tests/HelpTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -105,7 +105,7 @@ TEST_CASE("THelp: deprecatedOptions", "[help]") {
     CLI::App app{"My prog"};
 
     std::string x;
-    auto soption = app.add_option("--something", x, "My option here");
+    auto *soption = app.add_option("--something", x, "My option here");
     app.add_option("--something_else", x, "My option here");
     std::string y;
     app.add_option("--another", y);
@@ -159,7 +159,7 @@ TEST_CASE("THelp: retiredOptions", "[help]") {
     CLI::App app{"My prog"};
 
     std::string x;
-    auto opt1 = app.add_option("--something", x, "My option here");
+    auto *opt1 = app.add_option("--something", x, "My option here");
     app.add_option("--something_else", x, "My option here");
     std::string y;
     app.add_option("--another", y);
@@ -213,7 +213,7 @@ TEST_CASE("THelp: retiredOptions3", "[help]") {
 TEST_CASE("THelp: HiddenGroup", "[help]") {
     CLI::App app{"My prog"};
     // empty option group name should be hidden
-    auto hgroup = app.add_option_group("");
+    auto *hgroup = app.add_option_group("");
     std::string x;
     hgroup->add_option("something", x, "My option here");
     std::string y;
@@ -289,7 +289,8 @@ TEST_CASE("THelp: VectorOpts", "[help]") {
 
     std::string help = app.help();
 
-    CHECK_THAT(help, Contains("INT=[1,2] ..."));
+    CHECK_THAT(help, Contains("[1,2]"));
+    CHECK_THAT(help, Contains(" ..."));
 }
 
 TEST_CASE("THelp: MultiPosOpts", "[help]") {
@@ -394,18 +395,18 @@ TEST_CASE("THelp: ManualSetters", "[help]") {
 
     std::string help = app.help();
 
-    CHECK_THAT(help, Contains("=12"));
+    CHECK_THAT(help, Contains("[12]"));
     CHECK_THAT(help, Contains("BIGGLES"));
 
     op1->default_val("14");
     CHECK(14 == x);
     help = app.help();
-    CHECK_THAT(help, Contains("=14"));
+    CHECK_THAT(help, Contains("[14]"));
 
     op1->default_val(12);
     CHECK(12 == x);
     help = app.help();
-    CHECK_THAT(help, Contains("=12"));
+    CHECK_THAT(help, Contains("[12]"));
 
     CHECK(op1->get_run_callback_for_default());
     op1->run_callback_for_default(false);
@@ -415,7 +416,7 @@ TEST_CASE("THelp: ManualSetters", "[help]") {
     // x should not be modified in this case
     CHECK(12 == x);
     help = app.help();
-    CHECK_THAT(help, Contains("=18"));
+    CHECK_THAT(help, Contains("[18]"));
 }
 
 TEST_CASE("THelp: ManualSetterOverFunction", "[help]") {
@@ -432,7 +433,7 @@ TEST_CASE("THelp: ManualSetterOverFunction", "[help]") {
     CHECK(1 == x);
 
     std::string help = app.help();
-    CHECK_THAT(help, Contains("=12"));
+    CHECK_THAT(help, Contains("[12]"));
     CHECK_THAT(help, Contains("BIGGLES"));
     CHECK_THAT(help, Contains("QUIGGLES"));
     CHECK_THAT(help, Contains("{1,2}"));
@@ -441,7 +442,7 @@ TEST_CASE("THelp: ManualSetterOverFunction", "[help]") {
 TEST_CASE("THelp: Subcom", "[help]") {
     CLI::App app{"My prog"};
 
-    auto sub1 = app.add_subcommand("sub1");
+    auto *sub1 = app.add_subcommand("sub1");
     app.add_subcommand("sub2");
 
     std::string help = app.help();
@@ -455,8 +456,8 @@ TEST_CASE("THelp: Subcom", "[help]") {
     help = sub1->help();
     CHECK_THAT(help, Contains("Usage: sub1"));
 
-    char x[] = "./myprogram";
-    char y[] = "sub2";
+    char x[] = "./myprogram";  // NOLINT(modernize-avoid-c-arrays)
+    char y[] = "sub2";         // NOLINT(modernize-avoid-c-arrays)
 
     std::vector<char *> args = {x, y};
     app.parse(static_cast<int>(args.size()), args.data());
@@ -468,7 +469,7 @@ TEST_CASE("THelp: Subcom", "[help]") {
 TEST_CASE("THelp: Subcom_alias", "[help]") {
     CLI::App app{"My prog"};
 
-    auto sub1 = app.add_subcommand("sub1", "Subcommand1 description test");
+    auto *sub1 = app.add_subcommand("sub1", "Subcommand1 description test");
     sub1->alias("sub_alias1");
     sub1->alias("sub_alias2");
 
@@ -483,7 +484,7 @@ TEST_CASE("THelp: Subcom_alias", "[help]") {
 TEST_CASE("THelp: Subcom_alias_group", "[help]") {
     CLI::App app{"My prog"};
 
-    auto sub1 = app.add_subcommand("", "Subcommand1 description test");
+    auto *sub1 = app.add_subcommand("", "Subcommand1 description test");
     sub1->alias("sub_alias1");
     sub1->alias("sub_alias2");
 
@@ -498,7 +499,7 @@ TEST_CASE("THelp: Subcom_alias_group", "[help]") {
 TEST_CASE("THelp: MasterName", "[help]") {
     CLI::App app{"My prog", "MyRealName"};
 
-    char x[] = "./myprogram";
+    char x[] = "./myprogram";  // NOLINT(modernize-avoid-c-arrays)
 
     std::vector<char *> args = {x};
     app.parse(static_cast<int>(args.size()), args.data());
@@ -518,7 +519,7 @@ TEST_CASE("THelp: IntDefaults", "[help]") {
     CHECK_THAT(help, Contains("--one"));
     CHECK_THAT(help, Contains("--set"));
     CHECK_THAT(help, Contains("1"));
-    CHECK_THAT(help, Contains("=2"));
+    CHECK_THAT(help, Contains("[2]"));
     CHECK_THAT(help, Contains("2,3,4"));
 }
 
@@ -532,7 +533,7 @@ TEST_CASE("THelp: SetLower", "[help]") {
     std::string help = app.help();
 
     CHECK_THAT(help, Contains("--set"));
-    CHECK_THAT(help, Contains("=One"));
+    CHECK_THAT(help, Contains("[One]"));
     CHECK_THAT(help, Contains("oNe"));
     CHECK_THAT(help, Contains("twO"));
     CHECK_THAT(help, Contains("THREE"));
@@ -698,9 +699,9 @@ TEST_CASE("THelp: NiceName", "[help]") {
     CLI::App app;
 
     int x{0};
-    auto long_name = app.add_option("-s,--long,-q,--other,that", x);
-    auto short_name = app.add_option("more,-x,-y", x);
-    auto positional = app.add_option("posit", x);
+    auto *long_name = app.add_option("-s,--long,-q,--other,that", x);
+    auto *short_name = app.add_option("more,-x,-y", x);
+    auto *positional = app.add_option("posit", x);
 
     CHECK("--long" == long_name->get_name());
     CHECK("-x" == short_name->get_name());
@@ -767,25 +768,25 @@ struct CapturedHelp {
 
 TEST_CASE_METHOD(CapturedHelp, "Successful", "[help]") {
     CHECK(0 == run(CLI::Success()));
-    CHECK("" == out.str());
-    CHECK("" == err.str());
+    CHECK(out.str().empty());
+    CHECK(err.str().empty());
 }
 
 TEST_CASE_METHOD(CapturedHelp, "JustAnError", "[help]") {
     CHECK(42 == run(CLI::RuntimeError(42)));
-    CHECK("" == out.str());
-    CHECK("" == err.str());
+    CHECK(out.str().empty());
+    CHECK(err.str().empty());
 }
 
 TEST_CASE_METHOD(CapturedHelp, "CallForHelp", "[help]") {
     CHECK(0 == run(CLI::CallForHelp()));
     CHECK(app.help() == out.str());
-    CHECK("" == err.str());
+    CHECK(err.str().empty());
 }
 TEST_CASE_METHOD(CapturedHelp, "CallForAllHelp", "[help]") {
     CHECK(0 == run(CLI::CallForAllHelp()));
     CHECK(app.help("", CLI::AppFormatMode::All) == out.str());
-    CHECK("" == err.str());
+    CHECK(err.str().empty());
 }
 TEST_CASE_METHOD(CapturedHelp, "CallForAllHelpOutput", "[help]") {
     app.set_help_all_flag("--help-all", "Help all");
@@ -795,7 +796,7 @@ TEST_CASE_METHOD(CapturedHelp, "CallForAllHelpOutput", "[help]") {
 
     CHECK(0 == run(CLI::CallForAllHelp()));
     CHECK(app.help("", CLI::AppFormatMode::All) == out.str());
-    CHECK("" == err.str());
+    CHECK(err.str().empty());
     CHECK_THAT(out.str(), Contains("one"));
     CHECK_THAT(out.str(), Contains("two"));
     CHECK_THAT(out.str(), Contains("--three"));
@@ -812,18 +813,18 @@ TEST_CASE_METHOD(CapturedHelp, "CallForAllHelpOutput", "[help]") {
                        "  One description\n\n"
                        "two\n"
                        "  Options:\n"
-                       "    --three                     \n\n\n");
+                       "    --three                     \n\n");
 }
 TEST_CASE_METHOD(CapturedHelp, "NewFormattedHelp", "[help]") {
     app.formatter_fn([](const CLI::App *, std::string, CLI::AppFormatMode) { return "New Help"; });
     CHECK(0 == run(CLI::CallForHelp()));
     CHECK("New Help" == out.str());
-    CHECK("" == err.str());
+    CHECK(err.str().empty());
 }
 
 TEST_CASE_METHOD(CapturedHelp, "NormalError", "[help]") {
     CHECK(static_cast<int>(CLI::ExitCodes::ExtrasError) == run(CLI::ExtrasError({"Thing"})));
-    CHECK("" == out.str());
+    CHECK(out.str().empty());
     CHECK_THAT(err.str(), Contains("for more information"));
     CHECK_THAT(err.str(), !Contains("ExtrasError"));
     CHECK_THAT(err.str(), Contains("Thing"));
@@ -834,7 +835,7 @@ TEST_CASE_METHOD(CapturedHelp, "NormalError", "[help]") {
 TEST_CASE_METHOD(CapturedHelp, "DoubleError", "[help]") {
     app.set_help_all_flag("--help-all");
     CHECK(static_cast<int>(CLI::ExitCodes::ExtrasError) == run(CLI::ExtrasError({"Thing"})));
-    CHECK("" == out.str());
+    CHECK(out.str().empty());
     CHECK_THAT(err.str(), Contains("for more information"));
     CHECK_THAT(err.str(), Contains(" --help "));
     CHECK_THAT(err.str(), Contains(" --help-all "));
@@ -848,7 +849,7 @@ TEST_CASE_METHOD(CapturedHelp, "AllOnlyError", "[help]") {
     app.set_help_all_flag("--help-all");
     app.set_help_flag();
     CHECK(static_cast<int>(CLI::ExitCodes::ExtrasError) == run(CLI::ExtrasError({"Thing"})));
-    CHECK("" == out.str());
+    CHECK(out.str().empty());
     CHECK_THAT(err.str(), Contains("for more information"));
     CHECK_THAT(err.str(), !Contains(" --help "));
     CHECK_THAT(err.str(), Contains(" --help-all "));
@@ -862,7 +863,7 @@ TEST_CASE_METHOD(CapturedHelp, "ReplacedError", "[help]") {
     app.failure_message(CLI::FailureMessage::help);
 
     CHECK(static_cast<int>(CLI::ExitCodes::ExtrasError) == run(CLI::ExtrasError({"Thing"})));
-    CHECK("" == out.str());
+    CHECK(out.str().empty());
     CHECK_THAT(err.str(), !Contains("for more information"));
     CHECK_THAT(err.str(), Contains("ERROR: ExtrasError"));
     CHECK_THAT(err.str(), Contains("Thing"));
@@ -876,7 +877,7 @@ TEST_CASE("THelp: CustomDoubleOption", "[help]") {
 
     CLI::App app;
 
-    auto opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) {
+    auto *opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) {
         custom_opt = {stol(vals.at(0)), stod(vals.at(1))};
         return true;
     });
@@ -888,11 +889,19 @@ TEST_CASE("THelp: CustomDoubleOption", "[help]") {
 TEST_CASE("THelp: CheckEmptyTypeName", "[help]") {
     CLI::App app;
 
-    auto opt = app.add_flag("-f,--flag");
+    auto *opt = app.add_flag("-f,--flag");
     std::string name = opt->get_type_name();
     CHECK(name.empty());
 }
 
+TEST_CASE("THelp: FlagDefaults", "[help]") {
+    CLI::App app;
+
+    app.add_flag("-t,--not{false}")->default_str("false");
+    auto str = app.help();
+    CHECK_THAT(str, Contains("--not{false}"));
+}
+
 TEST_CASE("THelp: AccessDescription", "[help]") {
     CLI::App app{"My description goes here"};
 
@@ -912,7 +921,7 @@ TEST_CASE("THelp: AccessOptionDescription", "[help]") {
     CLI::App app{};
 
     int x{0};
-    auto opt = app.add_option("-a,--alpha", x, "My description goes here");
+    auto *opt = app.add_option("-a,--alpha", x, "My description goes here");
 
     CHECK("My description goes here" == opt->get_description());
 }
@@ -921,7 +930,7 @@ TEST_CASE("THelp: SetOptionDescriptionAfterCreation", "[help]") {
     CLI::App app{};
 
     int x{0};
-    auto opt = app.add_option("-a,--alpha", x);
+    auto *opt = app.add_option("-a,--alpha", x);
     opt->description("My description goes here");
 
     CHECK("My description goes here" == opt->get_description());
@@ -932,7 +941,7 @@ TEST_CASE("THelp: CleanNeeds", "[help]") {
     CLI::App app;
 
     int x{0};
-    auto a_name = app.add_option("-a,--alpha", x);
+    auto *a_name = app.add_option("-a,--alpha", x);
     app.add_option("-b,--boo", x)->needs(a_name);
 
     CHECK_THAT(app.help(), !Contains("Requires"));
@@ -965,6 +974,16 @@ TEST_CASE("THelp: GroupOrder", "[help]") {
     CHECK(aee_loc > zee_loc);
 }
 
+TEST_CASE("THelp: GroupNameError", "[help]") {
+    CLI::App app;
+
+    auto *f1 = app.add_flag("--one");
+    auto *f2 = app.add_flag("--two");
+
+    CHECK_THROWS_AS(f1->group("evil group name\non two lines"), CLI::IncorrectConstruction);
+    CHECK_THROWS_AS(f2->group(std::string(5, '\0')), CLI::IncorrectConstruction);
+}
+
 TEST_CASE("THelp: ValidatorsText", "[help]") {
     CLI::App app;
 
@@ -1162,7 +1181,9 @@ TEST_CASE("THelp: ChangingDefaults", "[help]") {
     x = {5, 6};
     std::string help = app.help();
 
-    CHECK_THAT(help, Contains("INT=[3,4] ..."));
+    CHECK_THAT(help, Contains("[[3,4]]"));
+    CHECK_THAT(help, Contains("..."));
+    CHECK_THAT(help, Contains("INT"));
     CHECK(x[0] == 5);
 }
 
@@ -1179,7 +1200,8 @@ TEST_CASE("THelp: ChangingDefaultsWithAutoCapture", "[help]") {
 
     std::string help = app.help();
 
-    CHECK_THAT(help, Contains("INT=[1,2] ..."));
+    CHECK_THAT(help, Contains("[[1,2]]"));
+    CHECK_THAT(help, Contains("..."));
 }
 
 TEST_CASE("THelp: FunctionDefaultString", "[help]") {
@@ -1194,7 +1216,7 @@ TEST_CASE("THelp: FunctionDefaultString", "[help]") {
 
     std::string help = app.help();
 
-    CHECK_THAT(help, Contains("INT=Powerful"));
+    CHECK_THAT(help, Contains("[Powerful]"));
 }
 
 TEST_CASE("TVersion: simple_flag", "[help]") {
@@ -1248,7 +1270,7 @@ TEST_CASE("TVersion: parse_throw", "[help]") {
     CHECK_THROWS_AS(app.parse("--version"), CLI::CallForVersion);
     CHECK_THROWS_AS(app.parse("--version --arg2 5"), CLI::CallForVersion);
 
-    auto ptr = app.get_version_ptr();
+    auto *ptr = app.get_version_ptr();
 
     ptr->ignore_case();
     try {
@@ -1257,7 +1279,7 @@ TEST_CASE("TVersion: parse_throw", "[help]") {
         CHECK_THAT(CLI11_VERSION, Catch::Equals(v.what()));
         CHECK(0 == v.get_exit_code());
         const auto &appc = app;
-        auto cptr = appc.get_version_ptr();
+        const auto *cptr = appc.get_version_ptr();
         CHECK(1U == cptr->count());
     }
 }
diff --git a/tests/HelpersTest.cpp b/tests/HelpersTest.cpp
index 7a497aa..5186b47 100644
--- a/tests/HelpersTest.cpp
+++ b/tests/HelpersTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -6,6 +6,8 @@
 
 #include "app_helper.hpp"
 
+#include <cmath>
+
 #include <array>
 #include <atomic>
 #include <complex>
@@ -27,7 +29,7 @@ std::ostream &operator<<(std::ostream &out, const Streamable &) { return out <<
 
 TEST_CASE("TypeTools: Streaming", "[helpers]") {
 
-    CHECK("" == CLI::detail::to_string(NotStreamable{}));
+    CHECK(CLI::detail::to_string(NotStreamable{}).empty());
 
     CHECK("Streamable" == CLI::detail::to_string(Streamable{}));
 
@@ -148,7 +150,7 @@ TEST_CASE("Split: Single", "[helpers]") {
 TEST_CASE("Split: Empty", "[helpers]") {
     auto out = CLI::detail::split("", '.');
     REQUIRE(out.size() == 1u);
-    CHECK(out.at(0) == "");
+    CHECK(out.at(0).empty());
 }
 
 TEST_CASE("String: InvalidName", "[helpers]") {
@@ -298,19 +300,40 @@ TEST_CASE("Validators: FileNotExists", "[helpers]") {
     CHECK(CLI::NonexistentPath(myfile).empty());
 }
 
+TEST_CASE("Validators: FilePathModifier", "[helpers]") {
+    std::string myfile{"../TestFileNotUsed_1.txt"};
+    bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a'));  // create file
+    CHECK(ok);
+    std::string filename = "TestFileNotUsed_1.txt";
+    CLI::FileOnDefaultPath defPath("../");
+    CHECK(defPath(filename).empty());
+    CHECK(filename == myfile);
+    std::string filename2 = "nonexistingfile.csv";
+    CHECK_FALSE(defPath(filename2).empty());
+    // check it didn't modify the string
+    CHECK(filename2 == "nonexistingfile.csv");
+    CHECK(defPath(filename).empty());
+    std::remove(myfile.c_str());
+    CHECK_FALSE(defPath(myfile).empty());
+    // now test the no error version
+    CLI::FileOnDefaultPath defPathNoFail("../", false);
+    CHECK(defPathNoFail(filename2).empty());
+    CHECK(filename2 == "nonexistingfile.csv");
+}
+
 TEST_CASE("Validators: FileIsDir", "[helpers]") {
     std::string mydir{"../tests"};
-    CHECK("" != CLI::ExistingFile(mydir));
+    CHECK(!CLI::ExistingFile(mydir).empty());
 }
 
 TEST_CASE("Validators: DirectoryExists", "[helpers]") {
     std::string mydir{"../tests"};
-    CHECK("" == CLI::ExistingDirectory(mydir));
+    CHECK(CLI::ExistingDirectory(mydir).empty());
 }
 
 TEST_CASE("Validators: DirectoryNotExists", "[helpers]") {
     std::string mydir{"nondirectory"};
-    CHECK("" != CLI::ExistingDirectory(mydir));
+    CHECK(!CLI::ExistingDirectory(mydir).empty());
 }
 
 TEST_CASE("Validators: DirectoryIsFile", "[helpers]") {
@@ -326,7 +349,7 @@ TEST_CASE("Validators: DirectoryIsFile", "[helpers]") {
 
 TEST_CASE("Validators: PathExistsDir", "[helpers]") {
     std::string mydir{"../tests"};
-    CHECK("" == CLI::ExistingPath(mydir));
+    CHECK(CLI::ExistingPath(mydir).empty());
 }
 
 TEST_CASE("Validators: PathExistsFile", "[helpers]") {
@@ -342,7 +365,7 @@ TEST_CASE("Validators: PathExistsFile", "[helpers]") {
 
 TEST_CASE("Validators: PathNotExistsDir", "[helpers]") {
     std::string mydir{"nonpath"};
-    CHECK("" != CLI::ExistingPath(mydir));
+    CHECK(!CLI::ExistingPath(mydir).empty());
 }
 
 TEST_CASE("Validators: IPValidate1", "[helpers]") {
@@ -502,6 +525,10 @@ TEST_CASE("Validators: ProgramNameSplit", "[helpers]") {
     res = CLI::detail::split_program_name(std::string("  ./") + std::string(myfile) + "    ");
     CHECK(std::string("./") + std::string(myfile) == res.first);
     CHECK(res.second.empty());
+
+    res = CLI::detail::split_program_name("'odd_program_name.exe --arg --arg2=5");
+    CHECK("'odd_program_name.exe" == res.first);
+    CHECK_FALSE(res.second.empty());
 }
 
 TEST_CASE("CheckedMultiply: Int", "[helpers]") {
@@ -520,68 +547,68 @@ TEST_CASE("CheckedMultiply: Int", "[helpers]") {
     REQUIRE(CLI::detail::checked_multiply(a, b));
     REQUIRE(0 == a);
 
-    a = std::numeric_limits<int>::max();
+    a = (std::numeric_limits<int>::max)();
     b = 1;
     REQUIRE(CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<int>::max() == a);
+    REQUIRE((std::numeric_limits<int>::max)() == a);
 
-    a = std::numeric_limits<int>::max();
+    a = (std::numeric_limits<int>::max)();
     b = 2;
     REQUIRE(!CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<int>::max() == a);
+    REQUIRE((std::numeric_limits<int>::max)() == a);
 
-    a = std::numeric_limits<int>::max();
+    a = (std::numeric_limits<int>::max)();
     b = -1;
     REQUIRE(CLI::detail::checked_multiply(a, b));
-    REQUIRE(-std::numeric_limits<int>::max() == a);
+    REQUIRE(-(std::numeric_limits<int>::max)() == a);
 
-    a = std::numeric_limits<int>::max();
-    b = std::numeric_limits<int>::max();
+    a = (std::numeric_limits<int>::max)();
+    b = (std::numeric_limits<int>::max)();
     REQUIRE(!CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<int>::max() == a);
+    REQUIRE((std::numeric_limits<int>::max)() == a);
 
-    a = std::numeric_limits<int>::min();
-    b = std::numeric_limits<int>::max();
+    a = (std::numeric_limits<int>::min)();
+    b = (std::numeric_limits<int>::max)();
     REQUIRE(!CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<int>::min() == a);
+    REQUIRE((std::numeric_limits<int>::min)() == a);
 
-    a = std::numeric_limits<int>::min();
+    a = (std::numeric_limits<int>::min)();
     b = 1;
     REQUIRE(CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<int>::min() == a);
+    REQUIRE((std::numeric_limits<int>::min)() == a);
 
-    a = std::numeric_limits<int>::min();
+    a = (std::numeric_limits<int>::min)();
     b = -1;
     REQUIRE(!CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<int>::min() == a);
+    REQUIRE((std::numeric_limits<int>::min)() == a);
 
-    b = std::numeric_limits<int>::min();
+    b = (std::numeric_limits<int>::min)();
     a = -1;
     REQUIRE(!CLI::detail::checked_multiply(a, b));
     REQUIRE(-1 == a);
 
-    a = std::numeric_limits<int>::min() / 100;
+    a = (std::numeric_limits<int>::min)() / 100;
     b = 99;
     REQUIRE(CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<int>::min() / 100 * 99 == a);
+    REQUIRE((std::numeric_limits<int>::min)() / 100 * 99 == a);
 
-    a = std::numeric_limits<int>::min() / 100;
+    a = (std::numeric_limits<int>::min)() / 100;
     b = -101;
     REQUIRE(!CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<int>::min() / 100 == a);
+    REQUIRE((std::numeric_limits<int>::min)() / 100 == a);
     a = 2;
-    b = std::numeric_limits<int>::min() / 2;
+    b = (std::numeric_limits<int>::min)() / 2;
     REQUIRE(CLI::detail::checked_multiply(a, b));
-    a = std::numeric_limits<int>::min() / 2;
+    a = (std::numeric_limits<int>::min)() / 2;
     b = 2;
     REQUIRE(CLI::detail::checked_multiply(a, b));
 
     a = 4;
-    b = std::numeric_limits<int>::min() / 4;
+    b = (std::numeric_limits<int>::min)() / 4;
     REQUIRE(CLI::detail::checked_multiply(a, b));
 
     a = 48;
-    b = std::numeric_limits<int>::min() / 48;
+    b = (std::numeric_limits<int>::min)() / 48;
     REQUIRE(CLI::detail::checked_multiply(a, b));
 }
 
@@ -601,25 +628,25 @@ TEST_CASE("CheckedMultiply: SizeT", "[helpers]") {
     REQUIRE(CLI::detail::checked_multiply(a, b));
     REQUIRE(0u == a);
 
-    a = std::numeric_limits<std::size_t>::max();
+    a = (std::numeric_limits<std::size_t>::max)();
     b = 1u;
     REQUIRE(CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<std::size_t>::max() == a);
+    REQUIRE((std::numeric_limits<std::size_t>::max)() == a);
 
-    a = std::numeric_limits<std::size_t>::max();
+    a = (std::numeric_limits<std::size_t>::max)();
     b = 2u;
     REQUIRE(!CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<std::size_t>::max() == a);
+    REQUIRE((std::numeric_limits<std::size_t>::max)() == a);
 
-    a = std::numeric_limits<std::size_t>::max();
-    b = std::numeric_limits<std::size_t>::max();
+    a = (std::numeric_limits<std::size_t>::max)();
+    b = (std::numeric_limits<std::size_t>::max)();
     REQUIRE(!CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<std::size_t>::max() == a);
+    REQUIRE((std::numeric_limits<std::size_t>::max)() == a);
 
-    a = std::numeric_limits<std::size_t>::max() / 100;
+    a = (std::numeric_limits<std::size_t>::max)() / 100;
     b = 99u;
     REQUIRE(CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<std::size_t>::max() / 100u * 99u == a);
+    REQUIRE((std::numeric_limits<std::size_t>::max)() / 100u * 99u == a);
 }
 
 TEST_CASE("CheckedMultiply: Float", "[helpers]") {
@@ -643,30 +670,30 @@ TEST_CASE("CheckedMultiply: Float", "[helpers]") {
     REQUIRE(CLI::detail::checked_multiply(a, b));
     REQUIRE(-INFINITY == Approx(a));
 
-    a = std::numeric_limits<float>::max() / 100.0F;
+    a = (std::numeric_limits<float>::max)() / 100.0F;
     b = 1.0F;
     REQUIRE(CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<float>::max() / 100.0F == Approx(a));
+    REQUIRE((std::numeric_limits<float>::max)() / 100.0F == Approx(a));
 
-    a = std::numeric_limits<float>::max() / 100.0F;
+    a = (std::numeric_limits<float>::max)() / 100.0F;
     b = 99.0F;
     REQUIRE(CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<float>::max() / 100.0F * 99.0F == Approx(a));
+    REQUIRE((std::numeric_limits<float>::max)() / 100.0F * 99.0F == Approx(a));
 
-    a = std::numeric_limits<float>::max() / 100.0F;
+    a = (std::numeric_limits<float>::max)() / 100.0F;
     b = 101;
     REQUIRE(!CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<float>::max() / 100.0F == Approx(a));
+    REQUIRE((std::numeric_limits<float>::max)() / 100.0F == Approx(a));
 
-    a = std::numeric_limits<float>::max() / 100.0F;
+    a = (std::numeric_limits<float>::max)() / 100.0F;
     b = -99;
     REQUIRE(CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<float>::max() / 100.0F * -99.0F == Approx(a));
+    REQUIRE((std::numeric_limits<float>::max)() / 100.0F * -99.0F == Approx(a));
 
-    a = std::numeric_limits<float>::max() / 100.0F;
+    a = (std::numeric_limits<float>::max)() / 100.0F;
     b = -101;
     REQUIRE(!CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<float>::max() / 100.0F == Approx(a));
+    REQUIRE((std::numeric_limits<float>::max)() / 100.0F == Approx(a));
 }
 
 TEST_CASE("CheckedMultiply: Double", "[helpers]") {
@@ -680,40 +707,40 @@ TEST_CASE("CheckedMultiply: Double", "[helpers]") {
     REQUIRE(CLI::detail::checked_multiply(a, b));
     REQUIRE(0 == Approx(a));
 
-    a = INFINITY;
+    a = std::numeric_limits<double>::infinity();
     b = 20;
     REQUIRE(CLI::detail::checked_multiply(a, b));
-    REQUIRE(INFINITY == Approx(a));
+    REQUIRE(std::numeric_limits<double>::infinity() == Approx(a));
 
     a = 2;
-    b = -INFINITY;
+    b = -std::numeric_limits<double>::infinity();
     REQUIRE(CLI::detail::checked_multiply(a, b));
-    REQUIRE(-INFINITY == Approx(a));
+    REQUIRE(-std::numeric_limits<double>::infinity() == Approx(a));
 
-    a = std::numeric_limits<double>::max() / 100;
+    a = (std::numeric_limits<double>::max)() / 100;
     b = 1;
     REQUIRE(CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<double>::max() / 100 == Approx(a));
+    REQUIRE((std::numeric_limits<double>::max)() / 100 == Approx(a));
 
-    a = std::numeric_limits<double>::max() / 100;
+    a = (std::numeric_limits<double>::max)() / 100;
     b = 99;
     REQUIRE(CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<double>::max() / 100 * 99 == Approx(a));
+    REQUIRE((std::numeric_limits<double>::max)() / 100 * 99 == Approx(a));
 
-    a = std::numeric_limits<double>::max() / 100;
+    a = (std::numeric_limits<double>::max)() / 100;
     b = 101;
     REQUIRE(!CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<double>::max() / 100 == Approx(a));
+    REQUIRE((std::numeric_limits<double>::max)() / 100 == Approx(a));
 
-    a = std::numeric_limits<double>::max() / 100;
+    a = (std::numeric_limits<double>::max)() / 100;
     b = -99;
     REQUIRE(CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<double>::max() / 100 * -99 == Approx(a));
+    REQUIRE((std::numeric_limits<double>::max)() / 100 * -99 == Approx(a));
 
-    a = std::numeric_limits<double>::max() / 100;
+    a = (std::numeric_limits<double>::max)() / 100;
     b = -101;
     REQUIRE(!CLI::detail::checked_multiply(a, b));
-    REQUIRE(std::numeric_limits<double>::max() / 100 == Approx(a));
+    REQUIRE((std::numeric_limits<double>::max)() / 100 == Approx(a));
 }
 
 // Yes, this is testing an app_helper :)
@@ -774,11 +801,11 @@ TEST_CASE("RegEx: Shorts", "[helpers]") {
 
     CHECK(CLI::detail::split_short("-a", name, value));
     CHECK(name == "a");
-    CHECK(value == "");
+    CHECK(value.empty());
 
     CHECK(CLI::detail::split_short("-B", name, value));
     CHECK(name == "B");
-    CHECK(value == "");
+    CHECK(value.empty());
 
     CHECK(CLI::detail::split_short("-cc", name, value));
     CHECK(name == "c");
@@ -800,11 +827,11 @@ TEST_CASE("RegEx: Longs", "[helpers]") {
 
     CHECK(CLI::detail::split_long("--a", name, value));
     CHECK(name == "a");
-    CHECK(value == "");
+    CHECK(value.empty());
 
     CHECK(CLI::detail::split_long("--thing", name, value));
     CHECK(name == "thing");
-    CHECK(value == "");
+    CHECK(value.empty());
 
     CHECK(CLI::detail::split_long("--some=thing", name, value));
     CHECK(name == "some");
@@ -825,7 +852,7 @@ TEST_CASE("RegEx: SplittingNew", "[helpers]") {
     CHECK_NOTHROW(std::tie(shorts, longs, pname) = CLI::detail::get_names({"--long", "-s", "-q", "--also-long"}));
     CHECK(longs == std::vector<std::string>({"long", "also-long"}));
     CHECK(shorts == std::vector<std::string>({"s", "q"}));
-    CHECK(pname == "");
+    CHECK(pname.empty());
 
     std::tie(shorts, longs, pname) = CLI::detail::get_names({"--long", "", "-s", "-q", "", "--also-long"});
     CHECK(longs == std::vector<std::string>({"long", "also-long"}));
@@ -1007,33 +1034,33 @@ TEST_CASE("Types: TypeName", "[helpers]") {
 }
 
 TEST_CASE("Types: OverflowSmall", "[helpers]") {
-    signed char x;
-    auto strmax = std::to_string(std::numeric_limits<signed char>::max() + 1);
+    signed char x = 0;
+    auto strmax = std::to_string((std::numeric_limits<signed char>::max)() + 1);
     CHECK_FALSE(CLI::detail::lexical_cast(strmax, x));
 
-    unsigned char y;
-    strmax = std::to_string(std::numeric_limits<unsigned char>::max() + 1);
+    unsigned char y = 0;
+    strmax = std::to_string((std::numeric_limits<unsigned char>::max)() + 1);
     CHECK_FALSE(CLI::detail::lexical_cast(strmax, y));
 }
 
 TEST_CASE("Types: LexicalCastInt", "[helpers]") {
     std::string signed_input = "-912";
-    int x_signed;
+    int x_signed = 0;
     CHECK(CLI::detail::lexical_cast(signed_input, x_signed));
     CHECK(x_signed == -912);
 
     std::string unsigned_input = "912";
-    unsigned int x_unsigned;
+    unsigned int x_unsigned = 0;
     CHECK(CLI::detail::lexical_cast(unsigned_input, x_unsigned));
     CHECK(x_unsigned == (unsigned int)912);
 
     CHECK_FALSE(CLI::detail::lexical_cast(signed_input, x_unsigned));
 
-    unsigned char y;
-    std::string overflow_input = std::to_string(std::numeric_limits<uint64_t>::max()) + "0";
+    unsigned char y = 0;
+    std::string overflow_input = std::to_string((std::numeric_limits<uint64_t>::max)()) + "0";
     CHECK_FALSE(CLI::detail::lexical_cast(overflow_input, y));
 
-    char y_signed;
+    char y_signed = 0;
     CHECK_FALSE(CLI::detail::lexical_cast(overflow_input, y_signed));
 
     std::string bad_input = "hello";
@@ -1042,6 +1069,10 @@ TEST_CASE("Types: LexicalCastInt", "[helpers]") {
     std::string extra_input = "912i";
     CHECK_FALSE(CLI::detail::lexical_cast(extra_input, y));
 
+    extra_input = "true";
+    CHECK(CLI::detail::lexical_cast(extra_input, x_signed));
+    CHECK(x_signed != 0);
+
     std::string empty_input{};
     CHECK_FALSE(CLI::detail::lexical_cast(empty_input, x_signed));
     CHECK_FALSE(CLI::detail::lexical_cast(empty_input, x_unsigned));
@@ -1050,14 +1081,14 @@ TEST_CASE("Types: LexicalCastInt", "[helpers]") {
 
 TEST_CASE("Types: LexicalCastDouble", "[helpers]") {
     std::string input = "9.12";
-    long double x;
+    long double x = NAN;
     CHECK(CLI::detail::lexical_cast(input, x));
     CHECK((float)x == Approx((float)9.12));
 
     std::string bad_input = "hello";
     CHECK_FALSE(CLI::detail::lexical_cast(bad_input, x));
 
-    std::string overflow_input = "1" + std::to_string(std::numeric_limits<long double>::max());
+    std::string overflow_input = "1" + std::to_string((std::numeric_limits<long double>::max)());
     CHECK(CLI::detail::lexical_cast(overflow_input, x));
     CHECK_FALSE(std::isfinite(x));
 
@@ -1070,7 +1101,7 @@ TEST_CASE("Types: LexicalCastDouble", "[helpers]") {
 
 TEST_CASE("Types: LexicalCastBool", "[helpers]") {
     std::string input = "false";
-    bool x;
+    bool x = false;
     CHECK(CLI::detail::lexical_cast(input, x));
     CHECK_FALSE(x);
 
@@ -1110,7 +1141,7 @@ TEST_CASE("Types: LexicalCastParsable", "[helpers]") {
 TEST_CASE("Types: LexicalCastEnum", "[helpers]") {
     enum t1 : signed char { v1 = 5, v3 = 7, v5 = -9 };
 
-    t1 output;
+    t1 output = v1;
     CHECK(CLI::detail::lexical_cast("-9", output));
     CHECK(v5 == output);
 
@@ -1205,6 +1236,8 @@ TEST_CASE("Types: LexicalConversionTuple3", "[helpers]") {
 TEST_CASE("Types: LexicalConversionTuple4", "[helpers]") {
     CLI::results_t input = {"9.12", "19", "18.6", "5.87"};
     std::array<double, 4> x;
+    auto tsize = CLI::detail::type_count<decltype(x)>::value;
+    CHECK(tsize == 4);
     bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
     CHECK(res);
     CHECK(19 == Approx(std::get<1>(x)));
diff --git a/tests/NewParseTest.cpp b/tests/NewParseTest.cpp
index 30a0c9f..ced35e6 100644
--- a/tests/NewParseTest.cpp
+++ b/tests/NewParseTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -8,6 +8,7 @@
 
 #include <complex>
 #include <cstdint>
+#include <utility>
 
 using Catch::Matchers::Contains;
 
@@ -158,18 +159,14 @@ TEST_CASE_METHOD(TApp, "ComplexSingleImagOption", "[newparse]") {
 class spair {
   public:
     spair() = default;
-    spair(const std::string &s1, const std::string &s2) : first(s1), second(s2) {}
+    spair(std::string s1, std::string s2) : first(std::move(s1)), second(std::move(s2)) {}
     std::string first{};
     std::string second{};
 };
-// an example of custom converter that can be used to add new parsing options
-// On MSVC and possibly some other new compilers this can be a free standing function without the template
-// specialization but this is compiler dependent
-namespace CLI {
-namespace detail {
-
-template <> bool lexical_cast<spair>(const std::string &input, spair &output) {
 
+// Example of a custom converter that can be used to add new parsing options.
+// It will be found via argument-dependent lookup, so should be in the same namespace as the `spair` type.
+bool lexical_cast(const std::string &input, spair &output) {
     auto sep = input.find_first_of(':');
     if((sep == std::string::npos) && (sep > 0)) {
         return false;
@@ -177,8 +174,6 @@ template <> bool lexical_cast<spair>(const std::string &input, spair &output) {
     output = {input.substr(0, sep), input.substr(sep + 1)};
     return true;
 }
-}  // namespace detail
-}  // namespace CLI
 
 TEST_CASE_METHOD(TApp, "custom_string_converter", "[newparse]") {
     spair val;
@@ -200,20 +195,111 @@ TEST_CASE_METHOD(TApp, "custom_string_converterFail", "[newparse]") {
     CHECK_THROWS_AS(run(), CLI::ConversionError);
 }
 
+/// Wrapper with an unconvenient interface
+template <class T> class badlywrapped {
+  public:
+    badlywrapped() : value() {}
+
+    CLI11_NODISCARD T get() const { return value; }
+
+    void set(T val) { value = val; }
+
+  private:
+    T value;
+};
+
+// Example of a custom converter for a template type.
+// It will be found via argument-dependent lookup, so should be in the same namespace as the `badlywrapped` type.
+template <class T> bool lexical_cast(const std::string &input, badlywrapped<T> &output) {
+    // This using declaration lets us use an unqualified call to lexical_cast below. This is important because
+    // unqualified call finds the proper overload via argument-dependent lookup, and thus it will be able to find
+    // an overload for `spair` type, which is not in `CLI::detail`.
+    using CLI::detail::lexical_cast;
+
+    T value;
+    if(!lexical_cast(input, value))
+        return false;
+    output.set(value);
+    return true;
+}
+
+TEST_CASE_METHOD(TApp, "custom_string_converter_flag", "[newparse]") {
+    badlywrapped<bool> val;
+    std::vector<badlywrapped<bool>> vals;
+    app.add_flag("-1", val);
+    app.add_flag("-2", vals);
+
+    val.set(false);
+    args = {"-1"};
+    run();
+    CHECK(true == val.get());
+
+    args = {"-2", "-2"};
+    run();
+    CHECK(2 == vals.size());
+    CHECK(true == vals[0].get());
+    CHECK(true == vals[1].get());
+}
+
+TEST_CASE_METHOD(TApp, "custom_string_converter_adl", "[newparse]") {
+    // This test checks that the lexical_cast calls route as expected.
+    badlywrapped<spair> val;
+
+    app.add_option("-d,--dual_string", val);
+
+    args = {"-d", "string1:string2"};
+
+    run();
+    CHECK("string1" == val.get().first);
+    CHECK("string2" == val.get().second);
+}
+
+/// Another wrapper to test that specializing CLI::detail::lexical_cast works
+struct anotherstring {
+    anotherstring() = default;
+    std::string s{};
+};
+
+// This is a custom converter done via specializing the CLI::detail::lexical_cast template. This was the recommended
+// mechanism for extending the library before, so we need to test it. Don't do this in your code, use
+// argument-dependent lookup as outlined in the examples for spair and template badlywrapped.
+namespace CLI {
+namespace detail {
+template <> bool lexical_cast<anotherstring>(const std::string &input, anotherstring &output) {
+    bool result = lexical_cast(input, output.s);
+    if(result)
+        output.s += "!";
+    return result;
+}
+}  // namespace detail
+}  // namespace CLI
+
+TEST_CASE_METHOD(TApp, "custom_string_converter_specialize", "[newparse]") {
+    anotherstring s;
+
+    app.add_option("-s", s);
+
+    args = {"-s", "something"};
+
+    run();
+    CHECK("something!" == s.s);
+}
+
 /// simple class to wrap another  with a very specific type constructor and assignment operators to test out some of the
 /// option assignments
 template <class X> class objWrapper {
   public:
     objWrapper() = default;
-    explicit objWrapper(X obj) : val_{obj} {};
+    explicit objWrapper(X obj) : val_{std::move(obj)} {};
     objWrapper(const objWrapper &ow) = default;
     template <class TT> objWrapper(const TT &obj) = delete;
     objWrapper &operator=(const objWrapper &) = default;
-    objWrapper &operator=(objWrapper &&) = default;
+    // noexcept not allowed below by GCC 4.8
+    objWrapper &operator=(objWrapper &&) = default;  // NOLINT(performance-noexcept-move-constructor)
     // delete all other assignment operators
     template <typename TT> void operator=(TT &&obj) = delete;
 
-    const X &value() const { return val_; }
+    CLI11_NODISCARD const X &value() const { return val_; }
 
   private:
     X val_{};
@@ -312,8 +398,8 @@ class dobjWrapper {
     explicit dobjWrapper(double obj) : dval_{obj} {};
     explicit dobjWrapper(int obj) : ival_{obj} {};
 
-    double dvalue() const { return dval_; }
-    int ivalue() const { return ival_; }
+    CLI11_NODISCARD double dvalue() const { return dval_; }
+    CLI11_NODISCARD int ivalue() const { return ival_; }
 
   private:
     double dval_{0.0};
@@ -357,7 +443,7 @@ template <class X> class AobjWrapper {
     // delete all other assignment operators
     template <typename TT> void operator=(TT &&obj) = delete;
 
-    const X &value() const { return val_; }
+    CLI11_NODISCARD const X &value() const { return val_; }
 
   private:
     X val_{};
@@ -389,13 +475,14 @@ TEST_CASE_METHOD(TApp, "uint16Wrapper", "[newparse]") {
 
 template <class T> class SimpleWrapper {
   public:
-    SimpleWrapper() : val_{} {};
-    explicit SimpleWrapper(const T &initial) : val_{initial} {};
+    SimpleWrapper() = default;
+
+    explicit SimpleWrapper(T initial) : val_{std::move(initial)} {};
     T &getRef() { return val_; }
     using value_type = T;
 
   private:
-    T val_;
+    T val_{};
 };
 
 TEST_CASE_METHOD(TApp, "wrapperInt", "[newparse]") {
@@ -434,13 +521,13 @@ TEST_CASE_METHOD(TApp, "wrapperwrapperString", "[newparse]") {
 
     run();
     auto v1 = wrap.getRef().getRef();
-    auto v2 = "arg";
+    const auto *v2 = "arg";
     CHECK(v2 == v1);
 }
 
 TEST_CASE_METHOD(TApp, "wrapperwrapperVector", "[newparse]") {
     SimpleWrapper<SimpleWrapper<std::vector<int>>> wrap;
-    auto opt = app.add_option("--val", wrap);
+    auto *opt = app.add_option("--val", wrap);
     args = {"--val", "1", "2", "3", "4"};
 
     run();
diff --git a/tests/OptionGroupTest.cpp b/tests/OptionGroupTest.cpp
index ac6684f..7c475e8 100644
--- a/tests/OptionGroupTest.cpp
+++ b/tests/OptionGroupTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -11,8 +11,8 @@ using Catch::Matchers::Contains;
 using vs_t = std::vector<std::string>;
 
 TEST_CASE_METHOD(TApp, "BasicOptionGroup", "[optiongroup]") {
-    auto ogroup = app.add_option_group("clusters");
-    int res;
+    auto *ogroup = app.add_option_group("clusters");
+    int res = 0;
     ogroup->add_option("--test1", res);
     ogroup->add_option("--test2", res);
     ogroup->add_option("--test3", res);
@@ -34,7 +34,7 @@ TEST_CASE_METHOD(TApp, "OptionGroupInvalidNames", "[optiongroup]") {
 }
 
 TEST_CASE_METHOD(TApp, "BasicOptionGroupExact", "[optiongroup]") {
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     int res{0};
     ogroup->add_option("--test1", res);
     ogroup->add_option("--test2", res);
@@ -58,7 +58,7 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroupExact", "[optiongroup]") {
 }
 
 TEST_CASE_METHOD(TApp, "BasicOptionGroupExactTooMany", "[optiongroup]") {
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     int res{0};
     ogroup->add_option("--test1", res);
     ogroup->add_option("--test2", res);
@@ -71,7 +71,7 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroupExactTooMany", "[optiongroup]") {
 }
 
 TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMax", "[optiongroup]") {
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     int res{0};
     ogroup->add_option("--test1", res);
     ogroup->add_option("--test2", res);
@@ -95,7 +95,7 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMax", "[optiongroup]") {
 }
 
 TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMaxDifferent", "[optiongroup]") {
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     int res{0};
     ogroup->add_option("--test1", res);
     ogroup->add_option("--test2", res);
@@ -123,7 +123,7 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMaxDifferent", "[optiongroup]") {
 }
 
 TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMaxDifferentReversed", "[optiongroup]") {
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     int res{0};
     ogroup->add_option("--test1", res);
     ogroup->add_option("--test2", res);
@@ -155,12 +155,12 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMaxDifferentReversed", "[optiongroup]
 }
 
 TEST_CASE_METHOD(TApp, "BasicOptionGroupMax", "[optiongroup]") {
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     int res{0};
     ogroup->add_option("--test1", res);
     ogroup->add_option("--test2", res);
     ogroup->add_option("--test3", res);
-    int val2;
+    int val2 = 0;
     app.add_option("--option", val2);
     ogroup->require_option(-2);
     args = {"--test1", "5"};
@@ -179,7 +179,7 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroupMax", "[optiongroup]") {
 }
 
 TEST_CASE_METHOD(TApp, "BasicOptionGroupMax1", "[optiongroup]") {
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     int res{0};
     ogroup->add_option("--test1", res);
     ogroup->add_option("--test2", res);
@@ -203,7 +203,7 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroupMax1", "[optiongroup]") {
 }
 
 TEST_CASE_METHOD(TApp, "BasicOptionGroupMin", "[optiongroup]") {
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     int res{0};
     ogroup->add_option("--test1", res);
     ogroup->add_option("--test2", res);
@@ -224,7 +224,7 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroupMin", "[optiongroup]") {
 }
 
 TEST_CASE_METHOD(TApp, "BasicOptionGroupExact2", "[optiongroup]") {
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     int res{0};
     ogroup->add_option("--test1", res);
     ogroup->add_option("--test2", res);
@@ -248,7 +248,7 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroupExact2", "[optiongroup]") {
 }
 
 TEST_CASE_METHOD(TApp, "BasicOptionGroupMin2", "[optiongroup]") {
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     int res{0};
     ogroup->add_option("--test1", res);
     ogroup->add_option("--test2", res);
@@ -271,13 +271,13 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroupMin2", "[optiongroup]") {
 TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMoved", "[optiongroup]") {
 
     int res{0};
-    auto opt1 = app.add_option("--test1", res);
-    auto opt2 = app.add_option("--test2", res);
-    auto opt3 = app.add_option("--test3", res);
+    auto *opt1 = app.add_option("--test1", res);
+    auto *opt2 = app.add_option("--test2", res);
+    auto *opt3 = app.add_option("--test3", res);
     int val2{0};
     app.add_option("--option", val2);
 
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     ogroup->require_option();
     ogroup->add_option(opt1);
     ogroup->add_option(opt2);
@@ -300,13 +300,13 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMoved", "[optiongroup]") {
 TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMovedAsGroup", "[optiongroup]") {
 
     int res{0};
-    auto opt1 = app.add_option("--test1", res);
-    auto opt2 = app.add_option("--test2", res);
-    auto opt3 = app.add_option("--test3", res);
+    auto *opt1 = app.add_option("--test1", res);
+    auto *opt2 = app.add_option("--test2", res);
+    auto *opt3 = app.add_option("--test3", res);
     int val2{0};
     app.add_option("--option", val2);
 
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     ogroup->require_option();
     ogroup->add_options(opt1, opt2, opt3);
 
@@ -328,17 +328,17 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMovedAsGroup", "[optiongroup]") {
 TEST_CASE_METHOD(TApp, "BasicOptionGroupAddFailures", "[optiongroup]") {
 
     int res{0};
-    auto opt1 = app.add_option("--test1", res);
+    auto *opt1 = app.add_option("--test1", res);
     app.set_config("--config");
     int val2{0};
     app.add_option("--option", val2);
 
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     CHECK_THROWS_AS(ogroup->add_options(app.get_config_ptr()), CLI::OptionAlreadyAdded);
     CHECK_THROWS_AS(ogroup->add_options(app.get_help_ptr()), CLI::OptionAlreadyAdded);
 
-    auto sub = app.add_subcommand("sub", "subcommand");
-    auto opt2 = sub->add_option("--option2", val2);
+    auto *sub = app.add_subcommand("sub", "subcommand");
+    auto *opt2 = sub->add_option("--option2", val2);
 
     CHECK_THROWS_AS(ogroup->add_option(opt2), CLI::OptionNotFound);
 
@@ -346,7 +346,7 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroupAddFailures", "[optiongroup]") {
 
     ogroup->add_option(opt1);
 
-    auto opt3 = app.add_option("--test1", res);
+    auto *opt3 = app.add_option("--test1", res);
 
     CHECK_THROWS_AS(ogroup->add_option(opt3), CLI::OptionAlreadyAdded);
 }
@@ -354,14 +354,14 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroupAddFailures", "[optiongroup]") {
 TEST_CASE_METHOD(TApp, "BasicOptionGroupScrewedUpMove", "[optiongroup]") {
 
     int res{0};
-    auto opt1 = app.add_option("--test1", res);
-    auto opt2 = app.add_option("--test2", res);
+    auto *opt1 = app.add_option("--test1", res);
+    auto *opt2 = app.add_option("--test2", res);
     int val2{0};
     app.add_option("--option", val2);
 
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     ogroup->require_option();
-    auto ogroup2 = ogroup->add_option_group("clusters2");
+    auto *ogroup2 = ogroup->add_option_group("clusters2");
     CHECK_THROWS_AS(ogroup2->add_options(opt1, opt2), CLI::OptionNotFound);
 
     CLI::Option_group EmptyGroup("description", "new group", nullptr);
@@ -371,7 +371,7 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroupScrewedUpMove", "[optiongroup]") {
 }
 
 TEST_CASE_METHOD(TApp, "InvalidOptions", "[optiongroup]") {
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     CLI::Option *opt = nullptr;
     CHECK_THROWS_AS(ogroup->excludes(opt), CLI::OptionNotFound);
     CLI::App *app_p = nullptr;
@@ -382,7 +382,7 @@ TEST_CASE_METHOD(TApp, "InvalidOptions", "[optiongroup]") {
 
 TEST_CASE_METHOD(TApp, "OptionGroupInheritedOptionDefaults", "[optiongroup]") {
     app.option_defaults()->ignore_case();
-    auto ogroup = app.add_option_group("clusters");
+    auto *ogroup = app.add_option_group("clusters");
     int res{0};
     ogroup->add_option("--test1", res);
 
@@ -421,7 +421,7 @@ struct ManyGroups : public TApp {
         g3->add_option("--val3", val3);
     }
 
-    void remove_required() {
+    void remove_required() {  // NOLINT(readability-make-member-function-const)
         g1->get_option("--name1")->required(false);
         g2->get_option("--name2")->required(false);
         g3->get_option("--name3")->required(false);
@@ -448,6 +448,12 @@ TEST_CASE_METHOD(ManyGroups, "SingleGroup", "[optiongroup]") {
     CHECK_THROWS_AS(run(), CLI::RequiredError);
 }
 
+TEST_CASE_METHOD(ManyGroups, "getGroup", "[optiongroup]") {
+    auto *mn = app.get_option_group("main");
+    CHECK(mn == main);
+    CHECK_THROWS_AS(app.get_option_group("notfound"), CLI::OptionNotFound);
+}
+
 TEST_CASE_METHOD(ManyGroups, "ExcludesGroup", "[optiongroup]") {
     // only 1 group can be used
     g1->excludes(g2);
@@ -486,8 +492,8 @@ TEST_CASE_METHOD(ManyGroups, "NeedsGroup", "[optiongroup]") {
 // test adding an option group with existing subcommands to an app
 TEST_CASE_METHOD(TApp, "ExistingSubcommandMatch", "[optiongroup]") {
     auto sshared = std::make_shared<CLI::Option_group>("documenting the subcommand", "sub1g", nullptr);
-    auto s1 = sshared->add_subcommand("sub1");
-    auto o1 = sshared->add_option_group("opt1");
+    auto *s1 = sshared->add_subcommand("sub1");
+    auto *o1 = sshared->add_option_group("opt1");
     o1->add_subcommand("sub3")->alias("sub4");
 
     app.add_subcommand("sub1");
@@ -597,9 +603,9 @@ TEST_CASE_METHOD(ManyGroups, "DisableFirst", "[optiongroup]") {
 TEST_CASE_METHOD(ManyGroups, "SameSubcommand", "[optiongroup]") {
     // only 1 group can be used if remove_required not used
     remove_required();
-    auto sub1 = g1->add_subcommand("sub1")->disabled();
-    auto sub2 = g2->add_subcommand("sub1")->disabled();
-    auto sub3 = g3->add_subcommand("sub1");
+    auto *sub1 = g1->add_subcommand("sub1")->disabled();
+    auto *sub2 = g2->add_subcommand("sub1")->disabled();
+    auto *sub3 = g3->add_subcommand("sub1");
     // so when the subcommands are disabled they can have the same name
     sub1->disabled(false);
     sub2->disabled(false);
@@ -679,7 +685,7 @@ TEST_CASE_METHOD(ManyGroups, "Inheritance", "[optiongroup]") {
     remove_required();
     g1->ignore_case();
     g1->ignore_underscore();
-    auto t2 = g1->add_subcommand("t2");
+    auto *t2 = g1->add_subcommand("t2");
     args = {"T2", "t_2"};
     CHECK(t2->get_ignore_underscore());
     CHECK(t2->get_ignore_case());
@@ -689,7 +695,7 @@ TEST_CASE_METHOD(ManyGroups, "Inheritance", "[optiongroup]") {
 
 TEST_CASE_METHOD(ManyGroups, "Moving", "[optiongroup]") {
     remove_required();
-    auto mg = app.add_option_group("maing");
+    auto *mg = app.add_option_group("maing");
     mg->add_subcommand(g1);
     mg->add_subcommand(g2);
 
@@ -767,11 +773,11 @@ TEST_CASE_METHOD(ManyGroupsPreTrigger, "PreTriggerTestsPositionals", "[optiongro
 
 TEST_CASE_METHOD(ManyGroupsPreTrigger, "PreTriggerTestsSubcommand", "[optiongroup]") {
 
-    auto sub1 = g1->add_subcommand("sub1")->fallthrough();
+    auto *sub1 = g1->add_subcommand("sub1")->fallthrough();
     g2->add_subcommand("sub2")->fallthrough();
     g3->add_subcommand("sub3")->fallthrough();
 
-    std::size_t subtrigger;
+    std::size_t subtrigger = 0;
     sub1->preparse_callback([&subtrigger](std::size_t count) { subtrigger = count; });
     args = {"sub1"};
     run();
diff --git a/tests/OptionTypeTest.cpp b/tests/OptionTypeTest.cpp
index 19b6974..3acdde4 100644
--- a/tests/OptionTypeTest.cpp
+++ b/tests/OptionTypeTest.cpp
@@ -1,11 +1,13 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
 // SPDX-License-Identifier: BSD-3-Clause
 
 #include "app_helper.hpp"
+
 #include <atomic>
+#include <cmath>
 #include <complex>
 #include <cstdint>
 #include <cstdlib>
@@ -17,8 +19,11 @@
 #include <set>
 #include <unordered_map>
 #include <unordered_set>
+#include <utility>
 #include <vector>
 
+using Catch::literals::operator"" _a;
+
 TEST_CASE_METHOD(TApp, "OneStringAgain", "[optiontype]") {
     std::string str;
     app.add_option("-s,--string", str);
@@ -44,13 +49,13 @@ TEST_CASE_METHOD(TApp, "doubleFunction", "[optiontype]") {
     app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
     args = {"--val", "-354.356"};
     run();
-    CHECK(300.356 == res);
+    CHECK(300.356_a == res);
     // get the original value as entered as an integer
-    CHECK(-354.356f == app["--val"]->as<float>());
+    CHECK(-354.356_a == app["--val"]->as<float>());
 }
 
 TEST_CASE_METHOD(TApp, "doubleFunctionFail", "[optiontype]") {
-    double res;
+    double res = NAN;
     app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
     args = {"--val", "not_double"};
     CHECK_THROWS_AS(run(), CLI::ConversionError);
@@ -65,8 +70,8 @@ TEST_CASE_METHOD(TApp, "doubleVectorFunction", "[optiontype]") {
     args = {"--val", "5", "--val", "6", "--val", "7"};
     run();
     CHECK(3u == res.size());
-    CHECK(10.0 == res[0]);
-    CHECK(12.0 == res[2]);
+    CHECK(10.0_a == res[0]);
+    CHECK(12.0_a == res[2]);
 }
 
 TEST_CASE_METHOD(TApp, "doubleVectorFunctionFail", "[optiontype]") {
@@ -86,7 +91,7 @@ TEST_CASE_METHOD(TApp, "doubleVectorFunctionFail", "[optiontype]") {
 
 TEST_CASE_METHOD(TApp, "doubleVectorFunctionRunCallbackOnDefault", "[optiontype]") {
     std::vector<double> res;
-    auto opt = app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
+    auto *opt = app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
         res = val;
         std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
     });
@@ -146,7 +151,7 @@ TEST_CASE_METHOD(TApp, "atomic_bool_flags", "[optiontype]") {
     std::atomic<int> iflag{0};
 
     app.add_flag("-b", bflag);
-    app.add_flag("-i,--int", iflag);
+    app.add_flag("-i,--int", iflag)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
 
     args = {"-b", "-i"};
     run();
@@ -195,7 +200,7 @@ TEST_CASE_METHOD(TApp, "BoolOption", "[optiontype]") {
 
 TEST_CASE_METHOD(TApp, "atomic_int_option", "[optiontype]") {
     std::atomic<int> i{0};
-    auto aopt = app.add_option("-i,--int", i);
+    auto *aopt = app.add_option("-i,--int", i);
     args = {"-i4"};
     run();
     CHECK(app.count("--int") == 1u);
@@ -240,7 +245,7 @@ TEST_CASE_METHOD(TApp, "CharOption", "[optiontype]") {
 
 TEST_CASE_METHOD(TApp, "vectorDefaults", "[optiontype]") {
     std::vector<int> vals{4, 5};
-    auto opt = app.add_option("--long", vals)->capture_default_str();
+    auto *opt = app.add_option("--long", vals)->capture_default_str();
 
     args = {"--long", "[1,2,3]"};
 
@@ -286,7 +291,7 @@ TEST_CASE_METHOD(TApp, "CallbackBoolFlags", "[optiontype]") {
 
     auto func = [&value]() { value = true; };
 
-    auto cback = app.add_flag_callback("--val", func);
+    auto *cback = app.add_flag_callback("--val", func);
     args = {"--val"};
     run();
     CHECK(value);
@@ -339,7 +344,7 @@ TEST_CASE_METHOD(TApp, "pair_check_take_first", "[optiontype]") {
     CHECK(CLI::ExistingFile(myfile).empty());
     std::pair<std::string, int> findex;
 
-    auto opt = app.add_option("--file", findex)->check(CLI::ExistingFile)->check(CLI::PositiveNumber);
+    auto *opt = app.add_option("--file", findex)->check(CLI::ExistingFile)->check(CLI::PositiveNumber);
     CHECK_THROWS_AS(opt->get_validator(3), CLI::OptionNotFound);
     opt->get_validator(0)->application_index(0);
     opt->get_validator(1)->application_index(1);
@@ -400,6 +405,86 @@ TEST_CASE_METHOD(TApp, "VectorIndexedValidator", "[optiontype]") {
     CHECK_THROWS_AS(run(), CLI::ValidationError);
 }
 
+TEST_CASE_METHOD(TApp, "IntegerOverFlowShort", "[optiontype]") {
+    std::int16_t A{0};
+    std::uint16_t B{0};
+
+    app.add_option("-a", A);
+    app.add_option("-b", B);
+
+    args = {"-a", "2626254242"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"-b", "2626254242"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"-b", "-26262"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"-b", "-262624262525"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+TEST_CASE_METHOD(TApp, "IntegerOverFlowInt", "[optiontype]") {
+    int A{0};
+    unsigned int B{0};
+
+    app.add_option("-a", A);
+    app.add_option("-b", B);
+
+    args = {"-a", "262625424225252"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"-b", "262625424225252"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"-b", "-2626225252"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"-b", "-26262426252525252"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+TEST_CASE_METHOD(TApp, "IntegerOverFlowLong", "[optiontype]") {
+    std::int32_t A{0};
+    std::uint32_t B{0};
+
+    app.add_option("-a", A);
+    app.add_option("-b", B);
+
+    args = {"-a", "1111111111111111111111111111"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"-b", "1111111111111111111111111111"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"-b", "-2626225252"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"-b", "-111111111111111111111111"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+TEST_CASE_METHOD(TApp, "IntegerOverFlowLongLong", "[optiontype]") {
+    std::int64_t A{0};
+    std::uint64_t B{0};
+
+    app.add_option("-a", A);
+    app.add_option("-b", B);
+
+    args = {"-a", "1111111111111111111111111111111111111111111111111111111111"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"-b", "1111111111111111111111111111111111111111111111111111111111"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"-b", "-2626225252"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"-b", "-111111111111111111111111111111111111111111111111111111111"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
 TEST_CASE_METHOD(TApp, "VectorUnlimString", "[optiontype]") {
     std::vector<std::string> strvec;
     std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
@@ -422,7 +507,7 @@ TEST_CASE_METHOD(TApp, "VectorUnlimString", "[optiontype]") {
 // From https://github.com/CLIUtils/CLI11/issues/420
 TEST_CASE_METHOD(TApp, "stringLikeTests", "[optiontype]") {
     struct nType {
-        explicit nType(const std::string &a_value) : m_value{a_value} {}
+        explicit nType(std::string a_value) : m_value{std::move(a_value)} {}
 
         explicit operator std::string() const { return std::string{"op str"}; }
 
@@ -492,7 +577,7 @@ TEST_CASE_METHOD(TApp, "CustomDoubleOption", "[optiontype]") {
 
     std::pair<int, double> custom_opt;
 
-    auto opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) {
+    auto *opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) {
         custom_opt = {stol(vals.at(0)), stod(vals.at(1))};
         return true;
     });
@@ -519,12 +604,32 @@ TEST_CASE_METHOD(TApp, "CustomDoubleOptionAlt", "[optiontype]") {
     CHECK(1.5 == Approx(custom_opt.second));
 }
 
+// now with tuple support this is possible
+TEST_CASE_METHOD(TApp, "floatPair", "[optiontype]") {
+
+    std::pair<float, float> custom_opt;
+
+    auto *opt = app.add_option("--fp", custom_opt)->delimiter(',');
+    opt->default_str("3.4,2.7");
+
+    args = {"--fp", "12", "1.5"};
+
+    run();
+    CHECK(12.0f == Approx(custom_opt.first));
+    CHECK(1.5f == Approx(custom_opt.second));
+    args = {};
+    opt->force_callback();
+    run();
+    CHECK(3.4f == Approx(custom_opt.first));
+    CHECK(2.7f == Approx(custom_opt.second));
+}
+
 // now with independent type sizes and expected this is possible
 TEST_CASE_METHOD(TApp, "vectorPair", "[optiontype]") {
 
     std::vector<std::pair<int, std::string>> custom_opt;
 
-    auto opt = app.add_option("--dict", custom_opt);
+    auto *opt = app.add_option("--dict", custom_opt);
 
     args = {"--dict", "1", "str1", "--dict", "3", "str3"};
 
@@ -554,11 +659,32 @@ TEST_CASE_METHOD(TApp, "vectorPairFail", "[optiontype]") {
     CHECK_THROWS_AS(run(), CLI::ConversionError);
 }
 
+TEST_CASE_METHOD(TApp, "vectorPairFail2", "[optiontype]") {
+
+    std::vector<std::pair<int, int>> custom_opt;
+
+    auto *opt = app.add_option("--pairs", custom_opt);
+
+    args = {"--pairs", "1", "2", "3", "4"};
+
+    run();
+    CHECK(custom_opt.size() == 2U);
+
+    args = {"--pairs", "1", "2", "3"};
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+    // now change the type size to explicitly allow 1 or 2
+    opt->type_size(1, 2);
+
+    run();
+    CHECK(custom_opt.size() == 2U);
+}
+
 TEST_CASE_METHOD(TApp, "vectorPairTypeRange", "[optiontype]") {
 
     std::vector<std::pair<int, std::string>> custom_opt;
 
-    auto opt = app.add_option("--dict", custom_opt);
+    auto *opt = app.add_option("--dict", custom_opt);
 
     opt->type_size(2, 1);  // just test switched arguments
     CHECK(1 == opt->get_type_size_min());
@@ -594,7 +720,7 @@ TEST_CASE_METHOD(TApp, "vectorTuple", "[optiontype]") {
 
     std::vector<std::tuple<int, std::string, double>> custom_opt;
 
-    auto opt = app.add_option("--dict", custom_opt);
+    auto *opt = app.add_option("--dict", custom_opt);
 
     args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"};
 
@@ -624,7 +750,7 @@ TEST_CASE_METHOD(TApp, "vectorVector", "[optiontype]") {
 
     std::vector<std::vector<int>> custom_opt;
 
-    auto opt = app.add_option("--dict", custom_opt);
+    auto *opt = app.add_option("--dict", custom_opt);
 
     args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
 
@@ -660,7 +786,7 @@ TEST_CASE_METHOD(TApp, "vectorVectorFixedSize", "[optiontype]") {
 
     std::vector<std::vector<int>> custom_opt;
 
-    auto opt = app.add_option("--dict", custom_opt)->type_size(4);
+    auto *opt = app.add_option("--dict", custom_opt)->type_size(4);
 
     args = {"--dict", "1", "2", "4", "3", "--dict", "3", "1", "2", "8"};
 
@@ -864,7 +990,7 @@ TEST_CASE_METHOD(TApp, "unknownContainerWrapper", "[optiontype]") {
     class vopt {
       public:
         vopt() = default;
-        explicit vopt(const std::vector<double> &vdub) : val_{vdub} {};
+        explicit vopt(std::vector<double> vdub) : val_{std::move(vdub)} {};
         std::vector<double> val_{};
     };
 
@@ -944,6 +1070,23 @@ TEST_CASE_METHOD(TApp, "OnParseCall", "[optiontype]") {
     CHECK(3 == cnt);
 }
 
+TEST_CASE_METHOD(TApp, "OnParseCallPositional", "[optiontype]") {
+
+    int cnt{0};
+
+    auto *opt = app.add_option("pos",
+                               [&cnt](const CLI::results_t &) {
+                                   ++cnt;
+                                   return true;
+                               })
+                    ->trigger_on_parse()
+                    ->allow_extra_args();
+    args = {"1", "2", "3"};
+    CHECK(opt->get_trigger_on_parse());
+    run();
+    CHECK(3 == cnt);
+}
+
 TEST_CASE_METHOD(TApp, "OnParseCallVector", "[optiontype]") {
 
     std::vector<std::string> vec;
diff --git a/tests/OptionalTest.cpp b/tests/OptionalTest.cpp
index 5ea20be..3d78e34 100644
--- a/tests/OptionalTest.cpp
+++ b/tests/OptionalTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -130,6 +130,27 @@ TEST_CASE_METHOD(TApp, "StdOptionalUint", "[optional]") {
                   CLI::detail::object_category::wrapper_value);
 }
 
+TEST_CASE_METHOD(TApp, "StdOptionalbool", "[optional]") {
+    std::optional<bool> opt{};
+    CHECK(!opt);
+    app.add_flag("--opt,!--no-opt", opt);
+    CHECK(!opt);
+    run();
+    CHECK(!opt);
+
+    args = {"--opt"};
+    run();
+    CHECK(opt);
+    CHECK(*opt);
+
+    args = {"--no-opt"};
+    run();
+    CHECK(opt);
+    CHECK_FALSE(*opt);
+    static_assert(CLI::detail::classify_object<std::optional<bool>>::value ==
+                  CLI::detail::object_category::wrapper_value);
+}
+
 #ifdef _MSC_VER
 #pragma warning(default : 4244)
 #endif
@@ -239,16 +260,19 @@ TEST_CASE_METHOD(TApp, "BoostOptionalEnumTest", "[optional]") {
     auto dstring = optptr->get_default_str();
     CHECK(dstring.empty());
     run();
-    CHECK(!opt);
+    auto checkOpt = static_cast<bool>(opt);
+    CHECK_FALSE(checkOpt);
 
     args = {"-v", "3"};
     run();
-    CHECK(opt);
+    checkOpt = static_cast<bool>(opt);
+    CHECK(checkOpt);
     CHECK(*opt == eval::val3);
     opt = {};
     args = {"--val", "1"};
     run();
-    CHECK(opt);
+    checkOpt = static_cast<bool>(opt);
+    CHECK(checkOpt);
     CHECK(*opt == eval::val1);
 }
 
diff --git a/tests/SetTest.cpp b/tests/SetTest.cpp
index 5d225ff..b326989 100644
--- a/tests/SetTest.cpp
+++ b/tests/SetTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -6,6 +6,7 @@
 
 #include "app_helper.hpp"
 #include <map>
+#include <memory>
 
 static_assert(CLI::is_shared_ptr<std::shared_ptr<int>>::value == true, "is_shared_ptr should work on shared pointers");
 static_assert(CLI::is_shared_ptr<int *>::value == false, "is_shared_ptr should work on pointers");
@@ -34,7 +35,7 @@ static_assert(CLI::detail::pair_adaptor<std::vector<std::pair<int, int>>>::value
 TEST_CASE_METHOD(TApp, "SimpleMaps", "[set]") {
     int value{0};
     std::map<std::string, int> map = {{"one", 1}, {"two", 2}};
-    auto opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
+    auto *opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
     args = {"-s", "one"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -78,9 +79,9 @@ TEST_CASE_METHOD(TApp, "StringStringMapNoModify", "[set]") {
 enum SimpleEnum { SE_one = 1, SE_two = 2 };
 
 TEST_CASE_METHOD(TApp, "EnumMap", "[set]") {
-    SimpleEnum value;
+    SimpleEnum value;  // NOLINT(cppcoreguidelines-init-variables)
     std::map<std::string, SimpleEnum> map = {{"one", SE_one}, {"two", SE_two}};
-    auto opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
+    auto *opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
     args = {"-s", "one"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -92,9 +93,9 @@ TEST_CASE_METHOD(TApp, "EnumMap", "[set]") {
 enum class SimpleEnumC { one = 1, two = 2 };
 
 TEST_CASE_METHOD(TApp, "EnumCMap", "[set]") {
-    SimpleEnumC value;
+    SimpleEnumC value;  // NOLINT(cppcoreguidelines-init-variables)
     std::map<std::string, SimpleEnumC> map = {{"one", SimpleEnumC::one}, {"two", SimpleEnumC::two}};
-    auto opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
+    auto *opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
     args = {"-s", "one"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -111,7 +112,7 @@ TEST_CASE_METHOD(TApp, "structMap", "[set]") {
     };
     std::string struct_name;
     std::map<std::string, struct tstruct> map = {{"sone", {4, 32.4, "foo"}}, {"stwo", {5, 99.7, "bar"}}};
-    auto opt = app.add_option("-s,--set", struct_name)->check(CLI::IsMember(map));
+    auto *opt = app.add_option("-s,--set", struct_name)->check(CLI::IsMember(map));
     args = {"-s", "sone"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -131,8 +132,8 @@ TEST_CASE_METHOD(TApp, "structMapChange", "[set]") {
     };
     std::string struct_name;
     std::map<std::string, struct tstruct> map = {{"sone", {4, 32.4, "foo"}}, {"stwo", {5, 99.7, "bar"}}};
-    auto opt = app.add_option("-s,--set", struct_name)
-                   ->transform(CLI::IsMember(map, CLI::ignore_case, CLI::ignore_underscore, CLI::ignore_space));
+    auto *opt = app.add_option("-s,--set", struct_name)
+                    ->transform(CLI::IsMember(map, CLI::ignore_case, CLI::ignore_underscore, CLI::ignore_space));
     args = {"-s", "s one"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -159,8 +160,8 @@ TEST_CASE_METHOD(TApp, "structMapNoChange", "[set]") {
     };
     std::string struct_name;
     std::map<std::string, struct tstruct> map = {{"sone", {4, 32.4, "foo"}}, {"stwo", {5, 99.7, "bar"}}};
-    auto opt = app.add_option("-s,--set", struct_name)
-                   ->check(CLI::IsMember(map, CLI::ignore_case, CLI::ignore_underscore, CLI::ignore_space));
+    auto *opt = app.add_option("-s,--set", struct_name)
+                    ->check(CLI::IsMember(map, CLI::ignore_case, CLI::ignore_underscore, CLI::ignore_space));
     args = {"-s", "SONE"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -184,9 +185,9 @@ TEST_CASE_METHOD(TApp, "NonCopyableMap", "[set]") {
 
     std::string map_name;
     std::map<std::string, std::unique_ptr<double>> map;
-    map["e1"] = std::unique_ptr<double>(new double(5.7));
-    map["e3"] = std::unique_ptr<double>(new double(23.8));
-    auto opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(&map));
+    map["e1"].reset(new double(5.7));
+    map["e3"].reset(new double(23.8));
+    auto *opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(&map));
     args = {"-s", "e1"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -202,9 +203,9 @@ TEST_CASE_METHOD(TApp, "NonCopyableMapWithFunction", "[set]") {
 
     std::string map_name;
     std::map<std::string, std::unique_ptr<double>> map;
-    map["e1"] = std::unique_ptr<double>(new double(5.7));
-    map["e3"] = std::unique_ptr<double>(new double(23.8));
-    auto opt = app.add_option("-s,--set", map_name)->transform(CLI::IsMember(&map, CLI::ignore_underscore));
+    map["e1"].reset(new double(5.7));
+    map["e3"].reset(new double(23.8));
+    auto *opt = app.add_option("-s,--set", map_name)->transform(CLI::IsMember(&map, CLI::ignore_underscore));
     args = {"-s", "e_1"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -220,9 +221,9 @@ TEST_CASE_METHOD(TApp, "NonCopyableMapNonStringMap", "[set]") {
 
     std::string map_name;
     std::map<int, std::unique_ptr<double>> map;
-    map[4] = std::unique_ptr<double>(new double(5.7));
-    map[17] = std::unique_ptr<double>(new double(23.8));
-    auto opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(&map));
+    map[4].reset(new double(5.7));
+    map[17].reset(new double(23.8));
+    auto *opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(&map));
     args = {"-s", "4"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -240,7 +241,7 @@ TEST_CASE_METHOD(TApp, "CopyableMapMove", "[set]") {
     std::map<int, double> map;
     map[4] = 5.7;
     map[17] = 23.8;
-    auto opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(std::move(map)));
+    auto *opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(std::move(map)));
     args = {"-s", "4"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -254,7 +255,7 @@ TEST_CASE_METHOD(TApp, "CopyableMapMove", "[set]") {
 
 TEST_CASE_METHOD(TApp, "SimpleSets", "[set]") {
     std::string value;
-    auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<std::string>({"one", "two", "three"})});
+    auto *opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<std::string>({"one", "two", "three"})});
     args = {"-s", "one"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -264,9 +265,9 @@ TEST_CASE_METHOD(TApp, "SimpleSets", "[set]") {
 }
 
 TEST_CASE_METHOD(TApp, "SimpleSetsPtrs", "[set]") {
-    auto set = std::shared_ptr<std::set<std::string>>(new std::set<std::string>{"one", "two", "three"});
+    auto set = std::make_shared<std::set<std::string>>(std::set<std::string>{"one", "two", "three"});
     std::string value;
-    auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{set});
+    auto *opt = app.add_option("-s,--set", value)->check(CLI::IsMember{set});
     args = {"-s", "one"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -286,7 +287,7 @@ TEST_CASE_METHOD(TApp, "SimpleSetsPtrs", "[set]") {
 
 TEST_CASE_METHOD(TApp, "SimiShortcutSets", "[set]") {
     std::string value;
-    auto opt = app.add_option("--set", value)->check(CLI::IsMember({"one", "two", "three"}));
+    auto *opt = app.add_option("--set", value)->check(CLI::IsMember({"one", "two", "three"}));
     args = {"--set", "one"};
     run();
     CHECK(app.count("--set") == 1u);
@@ -294,7 +295,7 @@ TEST_CASE_METHOD(TApp, "SimiShortcutSets", "[set]") {
     CHECK("one" == value);
 
     std::string value2;
-    auto opt2 = app.add_option("--set2", value2)->transform(CLI::IsMember({"One", "two", "three"}, CLI::ignore_case));
+    auto *opt2 = app.add_option("--set2", value2)->transform(CLI::IsMember({"One", "two", "three"}, CLI::ignore_case));
     args = {"--set2", "onE"};
     run();
     CHECK(app.count("--set2") == 1u);
@@ -302,8 +303,8 @@ TEST_CASE_METHOD(TApp, "SimiShortcutSets", "[set]") {
     CHECK("One" == value2);
 
     std::string value3;
-    auto opt3 = app.add_option("--set3", value3)
-                    ->transform(CLI::IsMember({"O_ne", "two", "three"}, CLI::ignore_case, CLI::ignore_underscore));
+    auto *opt3 = app.add_option("--set3", value3)
+                     ->transform(CLI::IsMember({"O_ne", "two", "three"}, CLI::ignore_case, CLI::ignore_underscore));
     args = {"--set3", "onE"};
     run();
     CHECK(app.count("--set3") == 1u);
@@ -312,10 +313,10 @@ TEST_CASE_METHOD(TApp, "SimiShortcutSets", "[set]") {
 }
 
 TEST_CASE_METHOD(TApp, "SetFromCharStarArrayVector", "[set]") {
-    constexpr const char *names[3]{"one", "two", "three"};
+    constexpr const char *names[3]{"one", "two", "three"};  // NOLINT(modernize-avoid-c-arrays)
     std::string value;
-    auto opt = app.add_option("-s,--set", value)
-                   ->check(CLI::IsMember{std::vector<std::string>(std::begin(names), std::end(names))});
+    auto *opt = app.add_option("-s,--set", value)
+                    ->check(CLI::IsMember{std::vector<std::string>(std::begin(names), std::end(names))});
     args = {"-s", "one"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -327,7 +328,7 @@ TEST_CASE_METHOD(TApp, "SetFromCharStarArrayVector", "[set]") {
 TEST_CASE_METHOD(TApp, "OtherTypeSets", "[set]") {
     int value{0};
     std::vector<int> set = {2, 3, 4};
-    auto opt = app.add_option("--set", value)->check(CLI::IsMember(set));
+    auto *opt = app.add_option("--set", value)->check(CLI::IsMember(set));
     args = {"--set", "3"};
     run();
     CHECK(app.count("--set") == 1u);
@@ -338,7 +339,7 @@ TEST_CASE_METHOD(TApp, "OtherTypeSets", "[set]") {
     CHECK_THROWS_AS(run(), CLI::ValidationError);
 
     std::vector<int> set2 = {-2, 3, 4};
-    auto opt2 = app.add_option("--set2", value)->transform(CLI::IsMember(set2, [](int x) { return std::abs(x); }));
+    auto *opt2 = app.add_option("--set2", value)->transform(CLI::IsMember(set2, [](int x) { return std::abs(x); }));
     args = {"--set2", "-3"};
     run();
     CHECK(app.count("--set2") == 1u);
@@ -360,7 +361,7 @@ TEST_CASE_METHOD(TApp, "OtherTypeSets", "[set]") {
 
 TEST_CASE_METHOD(TApp, "NumericalSets", "[set]") {
     int value{0};
-    auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<int>({1, 2, 3})});
+    auto *opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<int>({1, 2, 3})});
     args = {"-s", "1"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -491,10 +492,27 @@ TEST_CASE_METHOD(TApp, "FailSet", "[set]") {
     CHECK_THROWS_AS(run(), CLI::ValidationError);
 }
 
+TEST_CASE_METHOD(TApp, "shortStringCheck", "[set]") {
+
+    std::string choice;
+    app.add_option("-q,--quick", choice)->check([](const std::string &v) {
+        if(v.size() > 5) {
+            return std::string{"string too long"};
+        }
+        return std::string{};
+    });
+
+    args = {"--quick", "3"};
+    CHECK_NOTHROW(run());
+
+    args = {"--quick=hello_goodbye"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
 TEST_CASE_METHOD(TApp, "FailMutableSet", "[set]") {
 
     int choice{0};
-    auto vals = std::shared_ptr<std::set<int>>(new std::set<int>({1, 2, 3}));
+    auto vals = std::make_shared<std::set<int>>(std::set<int>{1, 2, 3});
     app.add_option("-q,--quick", choice)->check(CLI::IsMember(vals));
     app.add_option("-s,--slow", choice)->capture_default_str()->check(CLI::IsMember(vals));
 
@@ -554,7 +572,7 @@ TEST_CASE_METHOD(TApp, "InSetIgnoreCaseMutableValue", "[set]") {
 
 TEST_CASE_METHOD(TApp, "InSetIgnoreCasePointer", "[set]") {
 
-    std::set<std::string> *options = new std::set<std::string>{"one", "Two", "THREE"};
+    auto *options = new std::set<std::string>{"one", "Two", "THREE"};
     std::string choice;
     app.add_option("-q,--quick", choice)->transform(CLI::IsMember(*options, CLI::ignore_case));
 
@@ -584,7 +602,7 @@ TEST_CASE_METHOD(TApp, "InSetIgnoreCasePointer", "[set]") {
 
 TEST_CASE_METHOD(TApp, "NotInSetIgnoreCasePointer", "[set]") {
 
-    std::set<std::string> *options = new std::set<std::string>{"one", "Two", "THREE"};
+    auto *options = new std::set<std::string>{"one", "Two", "THREE"};
     std::string choice;
     app.add_option("-q,--quick", choice)->check(!CLI::IsMember(*options, CLI::ignore_case));
 
diff --git a/tests/SimpleTest.cpp b/tests/SimpleTest.cpp
index 3051a46..14d6558 100644
--- a/tests/SimpleTest.cpp
+++ b/tests/SimpleTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/tests/StringParseTest.cpp b/tests/StringParseTest.cpp
index a984248..cc1205f 100644
--- a/tests/StringParseTest.cpp
+++ b/tests/StringParseTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -24,7 +24,7 @@ TEST_CASE_METHOD(TApp, "ExistingExeCheck", "[stringparse]") {
     }
 
     app.parse(std::string("./") + std::string(tmpexe) +
-                  " --string=\"this is my quoted string\" -t 'qstring 2' -m=`\"quoted string\"`",
+                  R"( --string="this is my quoted string" -t 'qstring 2' -m=`"quoted string"`)",
               true);
     CHECK("this is my quoted string" == str);
     CHECK("qstring 2" == str2);
@@ -46,7 +46,7 @@ TEST_CASE_METHOD(TApp, "ExistingExeCheckWithSpace", "[stringparse]") {
     }
 
     app.parse(std::string("./") + std::string(tmpexe) +
-                  " --string=\"this is my quoted string\" -t 'qstring 2' -m=`\"quoted string\"`",
+                  R"( --string="this is my quoted string" -t 'qstring 2' -m=`"quoted string"`)",
               true);
     CHECK("this is my quoted string" == str);
     CHECK("qstring 2" == str2);
@@ -70,7 +70,7 @@ TEST_CASE_METHOD(TApp, "ExistingExeCheckWithLotsOfSpace", "[stringparse]") {
     }
 
     app.parse(std::string("./") + std::string(tmpexe) +
-                  " --string=\"this is my quoted string\" -t 'qstring 2' -m=`\"quoted string\"`",
+                  R"( --string="this is my quoted string" -t 'qstring 2' -m=`"quoted string"`)",
               true);
     CHECK("this is my quoted string" == str);
     CHECK("qstring 2" == str2);
@@ -89,6 +89,15 @@ TEST_CASE_METHOD(TApp, "ProgNameWithSpace", "[stringparse]") {
     CHECK(app.get_name() == "Foo Bar");
 }
 
+// From GitHub issue #739 https://github.com/CLIUtils/CLI11/issues/739
+TEST_CASE_METHOD(TApp, "ProgNameOnly", "[stringparse]") {
+
+    app.add_flag("--foo");
+    CHECK_NOTHROW(app.parse("\"C:\\example.exe\"", true));
+
+    CHECK(app.get_name() == "C:\\example.exe");
+}
+
 TEST_CASE_METHOD(TApp, "ProgNameWithSpaceEmbeddedQuote", "[stringparse]") {
 
     app.add_flag("--foo");
diff --git a/tests/SubcommandTest.cpp b/tests/SubcommandTest.cpp
index e1ffded..f087598 100644
--- a/tests/SubcommandTest.cpp
+++ b/tests/SubcommandTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -11,8 +11,8 @@ using Catch::Matchers::Contains;
 using vs_t = std::vector<std::string>;
 
 TEST_CASE_METHOD(TApp, "BasicSubcommands", "[subcom]") {
-    auto sub1 = app.add_subcommand("sub1");
-    auto sub2 = app.add_subcommand("sub2");
+    auto *sub1 = app.add_subcommand("sub1");
+    auto *sub2 = app.add_subcommand("sub2");
 
     CHECK(&app == sub1->get_parent());
 
@@ -21,7 +21,7 @@ TEST_CASE_METHOD(TApp, "BasicSubcommands", "[subcom]") {
     CHECK_THROWS_AS(app.get_subcommand("sub3"), CLI::OptionNotFound);
 
     run();
-    CHECK(app.get_subcommands().size() == 0u);
+    CHECK(app.get_subcommands().empty());
 
     args = {"sub1"};
     run();
@@ -29,7 +29,7 @@ TEST_CASE_METHOD(TApp, "BasicSubcommands", "[subcom]") {
     CHECK(app.get_subcommands().size() == 1u);
 
     app.clear();
-    CHECK(app.get_subcommands().size() == 0u);
+    CHECK(app.get_subcommands().empty());
 
     args = {"sub2"};
     run();
@@ -57,8 +57,8 @@ TEST_CASE_METHOD(TApp, "BasicSubcommands", "[subcom]") {
 TEST_CASE_METHOD(TApp, "MultiSubFallthrough", "[subcom]") {
 
     // No explicit fallthrough
-    auto sub1 = app.add_subcommand("sub1");
-    auto sub2 = app.add_subcommand("sub2");
+    auto *sub1 = app.add_subcommand("sub1");
+    auto *sub2 = app.add_subcommand("sub2");
 
     args = {"sub1", "sub2"};
     run();
@@ -96,7 +96,7 @@ TEST_CASE_METHOD(TApp, "MultiSubFallthrough", "[subcom]") {
 }
 
 TEST_CASE_METHOD(TApp, "CrazyNameSubcommand", "[subcom]") {
-    auto sub1 = app.add_subcommand("sub1");
+    auto *sub1 = app.add_subcommand("sub1");
     // name can be set to whatever
     CHECK_NOTHROW(sub1->name("crazy name with spaces"));
     args = {"crazy name with spaces"};
@@ -110,8 +110,8 @@ TEST_CASE_METHOD(TApp, "RequiredAndSubcommands", "[subcom]") {
 
     std::string baz;
     app.add_option("baz", baz, "Baz Description")->required()->capture_default_str();
-    auto foo = app.add_subcommand("foo");
-    auto bar = app.add_subcommand("bar");
+    auto *foo = app.add_subcommand("foo");
+    auto *bar = app.add_subcommand("bar");
 
     args = {"bar", "foo"};
     REQUIRE_NOTHROW(run());
@@ -138,7 +138,7 @@ TEST_CASE_METHOD(TApp, "RequiredAndSubcomFallthrough", "[subcom]") {
     std::string baz;
     app.add_option("baz", baz)->required();
     app.add_subcommand("foo");
-    auto bar = app.add_subcommand("bar");
+    auto *bar = app.add_subcommand("bar");
     app.fallthrough();
 
     args = {"other", "bar"};
@@ -153,16 +153,16 @@ TEST_CASE_METHOD(TApp, "RequiredAndSubcomFallthrough", "[subcom]") {
 TEST_CASE_METHOD(TApp, "FooFooProblem", "[subcom]") {
 
     std::string baz_str, other_str;
-    auto baz = app.add_option("baz", baz_str);
-    auto foo = app.add_subcommand("foo");
-    auto other = foo->add_option("other", other_str);
+    auto *baz = app.add_option("baz", baz_str);
+    auto *foo = app.add_subcommand("foo");
+    auto *other = foo->add_option("other", other_str);
 
     args = {"foo", "foo"};
     run();
     CHECK(*foo);
     CHECK(!*baz);
     CHECK(*other);
-    CHECK("" == baz_str);
+    CHECK(baz_str.empty());
     CHECK("foo" == other_str);
 
     baz_str = "";
@@ -173,12 +173,12 @@ TEST_CASE_METHOD(TApp, "FooFooProblem", "[subcom]") {
     CHECK(*baz);
     CHECK(!*other);
     CHECK("foo" == baz_str);
-    CHECK("" == other_str);
+    CHECK(other_str.empty());
 }
 
 TEST_CASE_METHOD(TApp, "DuplicateSubcommands", "[subcom]") {
 
-    auto foo = app.add_subcommand("foo");
+    auto *foo = app.add_subcommand("foo");
 
     args = {"foo", "foo"};
     run();
@@ -193,7 +193,7 @@ TEST_CASE_METHOD(TApp, "DuplicateSubcommands", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "DuplicateSubcommandCallbacks", "[subcom]") {
 
-    auto foo = app.add_subcommand("foo");
+    auto *foo = app.add_subcommand("foo");
     int count{0};
     foo->callback([&count]() { ++count; });
     foo->immediate_callback();
@@ -209,7 +209,7 @@ TEST_CASE_METHOD(TApp, "DuplicateSubcommandCallbacks", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "DuplicateSubcommandCallbacksValues", "[subcom]") {
 
-    auto foo = app.add_subcommand("foo");
+    auto *foo = app.add_subcommand("foo");
     int val{0};
     foo->add_option("--val", val);
     std::vector<int> vals;
@@ -230,9 +230,9 @@ TEST_CASE_METHOD(TApp, "DuplicateSubcommandCallbacksValues", "[subcom]") {
 }
 
 TEST_CASE_METHOD(TApp, "Callbacks", "[subcom]") {
-    auto sub1 = app.add_subcommand("sub1");
+    auto *sub1 = app.add_subcommand("sub1");
     sub1->callback([]() { throw CLI::Success(); });
-    auto sub2 = app.add_subcommand("sub2");
+    auto *sub2 = app.add_subcommand("sub2");
     bool val{false};
     sub2->callback([&val]() { val = true; });
 
@@ -245,15 +245,15 @@ TEST_CASE_METHOD(TApp, "Callbacks", "[subcom]") {
 TEST_CASE_METHOD(TApp, "CallbackOrder", "[subcom]") {
 
     std::vector<std::string> cb;
-    app.parse_complete_callback([&cb]() { cb.push_back("ac1"); });
-    app.final_callback([&cb]() { cb.push_back("ac2"); });
-    auto sub1 =
+    app.parse_complete_callback([&cb]() { cb.emplace_back("ac1"); });
+    app.final_callback([&cb]() { cb.emplace_back("ac2"); });
+    auto *sub1 =
         app.add_subcommand("sub1")
-            ->parse_complete_callback([&cb]() { cb.push_back("c1"); })
+            ->parse_complete_callback([&cb]() { cb.emplace_back("c1"); })
             ->preparse_callback([&cb](std::size_t v1) { cb.push_back(std::string("pc1-") + std::to_string(v1)); });
-    auto sub2 =
+    auto *sub2 =
         app.add_subcommand("sub2")
-            ->final_callback([&cb]() { cb.push_back("c2"); })
+            ->final_callback([&cb]() { cb.emplace_back("c2"); })
             ->preparse_callback([&cb](std::size_t v1) { cb.push_back(std::string("pc2-") + std::to_string(v1)); });
     app.preparse_callback([&cb](std::size_t v1) { cb.push_back(std::string("pa-") + std::to_string(v1)); });
 
@@ -291,9 +291,9 @@ TEST_CASE_METHOD(TApp, "CallbackOrder", "[subcom]") {
 TEST_CASE_METHOD(TApp, "CallbackOrder2", "[subcom]") {
 
     std::vector<std::string> cb;
-    app.add_subcommand("sub1")->parse_complete_callback([&cb]() { cb.push_back("sub1"); });
-    app.add_subcommand("sub2")->parse_complete_callback([&cb]() { cb.push_back("sub2"); });
-    app.add_subcommand("sub3")->parse_complete_callback([&cb]() { cb.push_back("sub3"); });
+    app.add_subcommand("sub1")->parse_complete_callback([&cb]() { cb.emplace_back("sub1"); });
+    app.add_subcommand("sub2")->parse_complete_callback([&cb]() { cb.emplace_back("sub2"); });
+    app.add_subcommand("sub3")->parse_complete_callback([&cb]() { cb.emplace_back("sub3"); });
 
     args = {"sub1", "sub2", "sub3", "sub1", "sub1", "sub2", "sub1"};
     run();
@@ -311,9 +311,9 @@ TEST_CASE_METHOD(TApp, "CallbackOrder2_withFallthrough", "[subcom]") {
 
     std::vector<std::string> cb;
 
-    app.add_subcommand("sub1")->parse_complete_callback([&cb]() { cb.push_back("sub1"); })->fallthrough();
-    app.add_subcommand("sub2")->parse_complete_callback([&cb]() { cb.push_back("sub2"); });
-    app.add_subcommand("sub3")->parse_complete_callback([&cb]() { cb.push_back("sub3"); });
+    app.add_subcommand("sub1")->parse_complete_callback([&cb]() { cb.emplace_back("sub1"); })->fallthrough();
+    app.add_subcommand("sub2")->parse_complete_callback([&cb]() { cb.emplace_back("sub2"); });
+    app.add_subcommand("sub3")->parse_complete_callback([&cb]() { cb.emplace_back("sub3"); });
 
     args = {"sub1", "sub2", "sub3", "sub1", "sub1", "sub2", "sub1"};
     run();
@@ -328,9 +328,9 @@ TEST_CASE_METHOD(TApp, "CallbackOrder2_withFallthrough", "[subcom]") {
 }
 
 TEST_CASE_METHOD(TApp, "RuntimeErrorInCallback", "[subcom]") {
-    auto sub1 = app.add_subcommand("sub1");
+    auto *sub1 = app.add_subcommand("sub1");
     sub1->callback([]() { throw CLI::RuntimeError(); });
-    auto sub2 = app.add_subcommand("sub2");
+    auto *sub2 = app.add_subcommand("sub2");
     sub2->callback([]() { throw CLI::RuntimeError(2); });
 
     args = {"sub1"};
@@ -403,7 +403,7 @@ TEST_CASE_METHOD(TApp, "NoFallThroughPositionalsWithTerminator", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "NamelessSubComPositionals", "[subcom]") {
 
-    auto sub = app.add_subcommand();
+    auto *sub = app.add_subcommand();
     int val{1};
     sub->add_option("val", val);
 
@@ -414,8 +414,8 @@ TEST_CASE_METHOD(TApp, "NamelessSubComPositionals", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "NamelessSubWithSub", "[subcom]") {
 
-    auto sub = app.add_subcommand();
-    auto subsub = sub->add_subcommand("val");
+    auto *sub = app.add_subcommand();
+    auto *subsub = sub->add_subcommand("val");
 
     args = {"val"};
     run();
@@ -425,12 +425,12 @@ TEST_CASE_METHOD(TApp, "NamelessSubWithSub", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "NamelessSubWithMultipleSub", "[subcom]") {
 
-    auto sub1 = app.add_subcommand();
-    auto sub2 = app.add_subcommand();
-    auto sub1sub1 = sub1->add_subcommand("val1");
-    auto sub1sub2 = sub1->add_subcommand("val2");
-    auto sub2sub1 = sub2->add_subcommand("val3");
-    auto sub2sub2 = sub2->add_subcommand("val4");
+    auto *sub1 = app.add_subcommand();
+    auto *sub2 = app.add_subcommand();
+    auto *sub1sub1 = sub1->add_subcommand("val1");
+    auto *sub1sub2 = sub1->add_subcommand("val2");
+    auto *sub2sub1 = sub2->add_subcommand("val3");
+    auto *sub2sub2 = sub2->add_subcommand("val4");
     args = {"val1"};
     run();
     CHECK(sub1sub1->parsed());
@@ -461,12 +461,12 @@ TEST_CASE_METHOD(TApp, "NamelessSubWithMultipleSub", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "Nameless4LayerDeep", "[subcom]") {
 
-    auto sub = app.add_subcommand();
-    auto ssub = sub->add_subcommand();
-    auto sssub = ssub->add_subcommand();
+    auto *sub = app.add_subcommand();
+    auto *ssub = sub->add_subcommand();
+    auto *sssub = ssub->add_subcommand();
 
-    auto ssssub = sssub->add_subcommand();
-    auto sssssub = ssssub->add_subcommand("val");
+    auto *ssssub = sssub->add_subcommand();
+    auto *sssssub = ssssub->add_subcommand("val");
 
     args = {"val"};
     run();
@@ -477,13 +477,13 @@ TEST_CASE_METHOD(TApp, "Nameless4LayerDeep", "[subcom]") {
 /// Put subcommands in some crazy pattern and make everything still works
 TEST_CASE_METHOD(TApp, "Nameless4LayerDeepMulti", "[subcom]") {
 
-    auto sub1 = app.add_subcommand();
-    auto sub2 = app.add_subcommand();
-    auto ssub1 = sub1->add_subcommand();
-    auto ssub2 = sub2->add_subcommand();
+    auto *sub1 = app.add_subcommand();
+    auto *sub2 = app.add_subcommand();
+    auto *ssub1 = sub1->add_subcommand();
+    auto *ssub2 = sub2->add_subcommand();
 
-    auto sssub1 = ssub1->add_subcommand();
-    auto sssub2 = ssub2->add_subcommand();
+    auto *sssub1 = ssub1->add_subcommand();
+    auto *sssub2 = ssub2->add_subcommand();
     sssub1->add_subcommand("val1");
     ssub2->add_subcommand("val2");
     sub2->add_subcommand("val3");
@@ -568,7 +568,7 @@ TEST_CASE_METHOD(TApp, "EvilParseFallthrough", "[subcom]") {
     int val1{0}, val2{0};
     app.add_option("--val1", val1);
 
-    auto sub = app.add_subcommand("sub");
+    auto *sub = app.add_subcommand("sub");
     sub->add_option("val2", val2);
 
     args = {"sub", "--val1", "1", "2"};
@@ -584,7 +584,7 @@ TEST_CASE_METHOD(TApp, "CallbackOrdering", "[subcom]") {
     int val{1}, sub_val{0};
     app.add_option("--val", val);
 
-    auto sub = app.add_subcommand("sub");
+    auto *sub = app.add_subcommand("sub");
     sub->callback([&val, &sub_val]() { sub_val = val; });
 
     args = {"sub", "--val=2"};
@@ -603,7 +603,7 @@ TEST_CASE_METHOD(TApp, "CallbackOrderingImmediate", "[subcom]") {
     int val{1}, sub_val{0};
     app.add_option("--val", val);
 
-    auto sub = app.add_subcommand("sub")->immediate_callback();
+    auto *sub = app.add_subcommand("sub")->immediate_callback();
     sub->callback([&val, &sub_val]() { sub_val = val; });
 
     args = {"sub", "--val=2"};
@@ -621,7 +621,7 @@ TEST_CASE_METHOD(TApp, "CallbackOrderingImmediateMain", "[subcom]") {
     app.fallthrough();
     int val{0}, sub_val{0};
 
-    auto sub = app.add_subcommand("sub");
+    auto *sub = app.add_subcommand("sub");
     sub->callback([&val, &sub_val]() {
         sub_val = val;
         val = 2;
@@ -652,7 +652,7 @@ TEST_CASE_METHOD(TApp, "CallbackOrderingImmediateModeOrder", "[subcom]") {
     std::vector<int> v;
     app.callback([&v]() { v.push_back(1); })->immediate_callback(true);
 
-    auto sub = app.add_subcommand("hello")->callback([&v]() { v.push_back(2); })->immediate_callback(false);
+    auto *sub = app.add_subcommand("hello")->callback([&v]() { v.push_back(2); })->immediate_callback(false);
     args = {"hello"};
     run();
     // immediate_callback inherited
@@ -682,21 +682,21 @@ TEST_CASE_METHOD(TApp, "RequiredSubCom", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "SubComExtras", "[subcom]") {
     app.allow_extras();
-    auto sub = app.add_subcommand("sub");
+    auto *sub = app.add_subcommand("sub");
 
     args = {"extra", "sub"};
     run();
     CHECK(std::vector<std::string>({"extra"}) == app.remaining());
-    CHECK(std::vector<std::string>() == sub->remaining());
+    CHECK(sub->remaining().empty());
 
     args = {"extra1", "extra2", "sub"};
     run();
     CHECK(std::vector<std::string>({"extra1", "extra2"}) == app.remaining());
-    CHECK(std::vector<std::string>() == sub->remaining());
+    CHECK(sub->remaining().empty());
 
     args = {"sub", "extra1", "extra2"};
     run();
-    CHECK(std::vector<std::string>() == app.remaining());
+    CHECK(app.remaining().empty());
     CHECK(std::vector<std::string>({"extra1", "extra2"}) == sub->remaining());
 
     args = {"extra1", "extra2", "sub", "extra3", "extra4"};
@@ -723,8 +723,8 @@ TEST_CASE_METHOD(TApp, "Required1SubCom", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "BadSubcommandSearch", "[subcom]") {
 
-    auto one = app.add_subcommand("one");
-    auto two = one->add_subcommand("two");
+    auto *one = app.add_subcommand("one");
+    auto *two = one->add_subcommand("two");
 
     CHECK_THROWS_AS(app.get_subcommand(two), CLI::OptionNotFound);
     CHECK_THROWS_AS(app.get_subcommand_ptr(two), CLI::OptionNotFound);
@@ -770,7 +770,7 @@ TEST_CASE_METHOD(TApp, "PrefixSeparation", "[subcom]") {
 }
 
 TEST_CASE_METHOD(TApp, "PrefixSubcom", "[subcom]") {
-    auto subc = app.add_subcommand("subc");
+    auto *subc = app.add_subcommand("subc");
     subc->prefix_command();
 
     app.add_flag("--simple");
@@ -785,7 +785,7 @@ TEST_CASE_METHOD(TApp, "PrefixSubcom", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "InheritHelpAllFlag", "[subcom]") {
     app.set_help_all_flag("--help-all");
-    auto subc = app.add_subcommand("subc");
+    auto *subc = app.add_subcommand("subc");
     auto help_opt_list = subc->get_options([](const CLI::Option *opt) { return opt->get_name() == "--help-all"; });
     CHECK(1u == help_opt_list.size());
 }
@@ -908,21 +908,25 @@ TEST_CASE_METHOD(SubcommandProgram, "Subcommand CaseCheck", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "SubcomInheritCaseCheck", "[subcom]") {
     app.ignore_case();
-    auto sub1 = app.add_subcommand("sub1");
-    auto sub2 = app.add_subcommand("sub2");
+    auto *sub1 = app.add_subcommand("sub1");
+    auto *sub2 = app.add_subcommand("sub2");
 
     run();
-    CHECK(app.get_subcommands().size() == 0u);
+    CHECK(app.get_subcommands().empty());
     CHECK(app.get_subcommands({}).size() == 2u);
     CHECK(app.get_subcommands([](const CLI::App *s) { return s->get_name() == "sub1"; }).size() == 1u);
 
+    // check the const version of get_subcommands
+    const auto &app_const = app;
+    CHECK(app_const.get_subcommands([](const CLI::App *s) { return s->get_name() == "sub1"; }).size() == 1u);
+
     args = {"SuB1"};
     run();
     CHECK(app.get_subcommands().at(0) == sub1);
     CHECK(app.get_subcommands().size() == 1u);
 
     app.clear();
-    CHECK(app.get_subcommands().size() == 0u);
+    CHECK(app.get_subcommands().empty());
 
     args = {"sUb2"};
     run();
@@ -945,11 +949,11 @@ TEST_CASE_METHOD(SubcommandProgram, "Subcommand UnderscoreCheck", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "SubcomInheritUnderscoreCheck", "[subcom]") {
     app.ignore_underscore();
-    auto sub1 = app.add_subcommand("sub_option1");
-    auto sub2 = app.add_subcommand("sub_option2");
+    auto *sub1 = app.add_subcommand("sub_option1");
+    auto *sub2 = app.add_subcommand("sub_option2");
 
     run();
-    CHECK(app.get_subcommands().size() == 0u);
+    CHECK(app.get_subcommands().empty());
     CHECK(app.get_subcommands({}).size() == 2u);
     CHECK(app.get_subcommands([](const CLI::App *s) { return s->get_name() == "sub_option1"; }).size() == 1u);
 
@@ -959,7 +963,7 @@ TEST_CASE_METHOD(TApp, "SubcomInheritUnderscoreCheck", "[subcom]") {
     CHECK(app.get_subcommands().size() == 1u);
 
     app.clear();
-    CHECK(app.get_subcommands().size() == 0u);
+    CHECK(app.get_subcommands().empty());
 
     args = {"_suboption2"};
     run();
@@ -1160,23 +1164,23 @@ TEST_CASE_METHOD(ManySubcommands, "RemoveSub", "[subcom]") {
 }
 
 TEST_CASE_METHOD(ManySubcommands, "RemoveSubFail", "[subcom]") {
-    auto sub_sub = sub1->add_subcommand("subsub");
+    auto *sub_sub = sub1->add_subcommand("subsub");
     CHECK(!app.remove_subcommand(sub_sub));
     CHECK(sub1->remove_subcommand(sub_sub));
     CHECK(!app.remove_subcommand(nullptr));
 }
 
 TEST_CASE_METHOD(ManySubcommands, "manyIndexQuery", "[subcom]") {
-    auto s1 = app.get_subcommand(0);
-    auto s2 = app.get_subcommand(1);
-    auto s3 = app.get_subcommand(2);
-    auto s4 = app.get_subcommand(3);
+    auto *s1 = app.get_subcommand(0);
+    auto *s2 = app.get_subcommand(1);
+    auto *s3 = app.get_subcommand(2);
+    auto *s4 = app.get_subcommand(3);
     CHECK(sub1 == s1);
     CHECK(sub2 == s2);
     CHECK(sub3 == s3);
     CHECK(sub4 == s4);
     CHECK_THROWS_AS(app.get_subcommand(4), CLI::OptionNotFound);
-    auto s0 = app.get_subcommand();
+    auto *s0 = app.get_subcommand();
     CHECK(sub1 == s0);
 }
 
@@ -1192,6 +1196,18 @@ TEST_CASE_METHOD(ManySubcommands, "manyIndexQueryPtr", "[subcom]") {
     CHECK_THROWS_AS(app.get_subcommand_ptr(4), CLI::OptionNotFound);
 }
 
+TEST_CASE_METHOD(ManySubcommands, "manyIndexQueryPtrByName", "[subcom]") {
+    auto s1 = app.get_subcommand_ptr("sub1");
+    auto s2 = app.get_subcommand_ptr("sub2");
+    auto s3 = app.get_subcommand_ptr("sub3");
+    auto s4 = app.get_subcommand_ptr("sub4");
+    CHECK(sub1 == s1.get());
+    CHECK(sub2 == s2.get());
+    CHECK(sub3 == s3.get());
+    CHECK(sub4 == s4.get());
+    CHECK_THROWS_AS(app.get_subcommand_ptr("sub5"), CLI::OptionNotFound);
+}
+
 TEST_CASE_METHOD(ManySubcommands, "Required1Fuzzy", "[subcom]") {
 
     app.require_subcommand(0, 1);
@@ -1220,17 +1236,17 @@ TEST_CASE_METHOD(ManySubcommands, "Required2Fuzzy", "[subcom]") {
 
 TEST_CASE_METHOD(ManySubcommands, "Unlimited", "[subcom]") {
     run();
-    CHECK(vs_t() == app.remaining(true));
+    CHECK(app.remaining(true).empty());
 
     app.require_subcommand();
 
     run();
-    CHECK(vs_t() == app.remaining(true));
+    CHECK(app.remaining(true).empty());
 
     app.require_subcommand(2, 0);  // 2 or more
 
     run();
-    CHECK(vs_t() == app.remaining(true));
+    CHECK(app.remaining(true).empty());
 }
 
 TEST_CASE_METHOD(ManySubcommands, "HelpFlags", "[subcom]") {
@@ -1296,7 +1312,7 @@ TEST_CASE_METHOD(ManySubcommands, "SubcommandExclusion", "[subcom]") {
 
 TEST_CASE_METHOD(ManySubcommands, "SubcommandOptionExclusion", "[subcom]") {
 
-    auto excluder_flag = app.add_flag("--exclude");
+    auto *excluder_flag = app.add_flag("--exclude");
     sub1->excludes(excluder_flag)->fallthrough();
     sub2->excludes(excluder_flag)->fallthrough();
     sub3->fallthrough();
@@ -1347,7 +1363,7 @@ TEST_CASE_METHOD(ManySubcommands, "SubcommandNeeds", "[subcom]") {
 
 TEST_CASE_METHOD(ManySubcommands, "SubcommandNeedsOptions", "[subcom]") {
 
-    auto opt = app.add_flag("--subactive");
+    auto *opt = app.add_flag("--subactive");
     sub1->needs(opt);
     sub1->fallthrough();
     args = {"sub1", "--subactive"};
@@ -1359,7 +1375,7 @@ TEST_CASE_METHOD(ManySubcommands, "SubcommandNeedsOptions", "[subcom]") {
     args = {"--subactive"};
     CHECK_NOTHROW(run());
 
-    auto opt2 = app.add_flag("--subactive2");
+    auto *opt2 = app.add_flag("--subactive2");
 
     sub1->needs(opt2);
     args = {"sub1", "--subactive"};
@@ -1375,7 +1391,7 @@ TEST_CASE_METHOD(ManySubcommands, "SubcommandNeedsOptions", "[subcom]") {
 
 TEST_CASE_METHOD(ManySubcommands, "SubcommandNeedsOptionsCallbackOrdering", "[subcom]") {
     int count{0};
-    auto opt = app.add_flag("--subactive");
+    auto *opt = app.add_flag("--subactive");
     app.add_flag("--flag1");
     sub1->needs(opt);
     sub1->fallthrough();
@@ -1396,8 +1412,8 @@ TEST_CASE_METHOD(ManySubcommands, "SubcommandNeedsOptionsCallbackOrdering", "[su
 
 TEST_CASE_METHOD(ManySubcommands, "SubcommandNeedsFail", "[subcom]") {
 
-    auto opt = app.add_flag("--subactive");
-    auto opt2 = app.add_flag("--dummy");
+    auto *opt = app.add_flag("--subactive");
+    auto *opt2 = app.add_flag("--dummy");
     sub1->needs(opt);
     CHECK_THROWS_AS(sub1->needs((CLI::Option *)nullptr), CLI::OptionNotFound);
     CHECK_THROWS_AS(sub1->needs((CLI::App *)nullptr), CLI::OptionNotFound);
@@ -1491,20 +1507,20 @@ TEST_CASE_METHOD(ManySubcommands, "SubcommandSilence", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "UnnamedSub", "[subcom]") {
     double val{0.0};
-    auto sub = app.add_subcommand("", "empty name");
-    auto opt = sub->add_option("-v,--value", val);
+    auto *sub = app.add_subcommand("", "empty name");
+    auto *opt = sub->add_option("-v,--value", val);
     args = {"-v", "4.56"};
 
     run();
     CHECK(4.56 == val);
     // make sure unnamed sub options can be found from the main app
-    auto opt2 = app.get_option("-v");
+    auto *opt2 = app.get_option("-v");
     CHECK(opt2 == opt);
 
     CHECK_THROWS_AS(app.get_option("--vvvv"), CLI::OptionNotFound);
     // now test in the constant context
     const auto &appC = app;
-    auto opt3 = appC.get_option("-v");
+    const auto *opt3 = appC.get_option("-v");
     CHECK("--value" == opt3->get_name());
     CHECK_THROWS_AS(appC.get_option("--vvvv"), CLI::OptionNotFound);
 }
@@ -1512,9 +1528,9 @@ TEST_CASE_METHOD(TApp, "UnnamedSub", "[subcom]") {
 TEST_CASE_METHOD(TApp, "UnnamedSubMix", "[subcom]") {
     double val{0.0}, val2{0.0}, val3{0.0};
     app.add_option("-t", val2);
-    auto sub1 = app.add_subcommand("", "empty name");
+    auto *sub1 = app.add_subcommand("", "empty name");
     sub1->add_option("-v,--value", val);
-    auto sub2 = app.add_subcommand("", "empty name2");
+    auto *sub2 = app.add_subcommand("", "empty name2");
     sub2->add_option("-m,--mix", val3);
     args = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
 
@@ -1528,7 +1544,7 @@ TEST_CASE_METHOD(TApp, "UnnamedSubMix", "[subcom]") {
 TEST_CASE_METHOD(TApp, "UnnamedSubMixExtras", "[subcom]") {
     double val{0.0}, val2{0.0};
     app.add_option("-t", val2);
-    auto sub = app.add_subcommand("", "empty name");
+    auto *sub = app.add_subcommand("", "empty name");
     sub->add_option("-v,--value", val);
     args = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
     app.allow_extras();
@@ -1542,7 +1558,7 @@ TEST_CASE_METHOD(TApp, "UnnamedSubMixExtras", "[subcom]") {
 TEST_CASE_METHOD(TApp, "UnnamedSubNoExtras", "[subcom]") {
     double val{0.0}, val2{0.0};
     app.add_option("-t", val2);
-    auto sub = app.add_subcommand();
+    auto *sub = app.add_subcommand();
     sub->add_option("-v,--value", val);
     args = {"-t", "5.93", "-v", "-3"};
     run();
@@ -1554,7 +1570,7 @@ TEST_CASE_METHOD(TApp, "UnnamedSubNoExtras", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "SubcommandAlias", "[subcom]") {
     double val{0.0};
-    auto sub = app.add_subcommand("sub1");
+    auto *sub = app.add_subcommand("sub1");
     sub->alias("sub2");
     sub->alias("sub3");
     sub->add_option("-v,--value", val);
@@ -1570,7 +1586,7 @@ TEST_CASE_METHOD(TApp, "SubcommandAlias", "[subcom]") {
     run();
     CHECK(7 == val);
 
-    auto &al = sub->get_aliases();
+    const auto &al = sub->get_aliases();
     REQUIRE(2U <= al.size());
 
     CHECK("sub2" == al[0]);
@@ -1582,7 +1598,7 @@ TEST_CASE_METHOD(TApp, "SubcommandAlias", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "SubcommandAliasIgnoreCaseUnderscore", "[subcom]") {
     double val{0.0};
-    auto sub = app.add_subcommand("sub1");
+    auto *sub = app.add_subcommand("sub1");
     sub->alias("sub2");
     sub->alias("sub3");
     sub->ignore_case();
@@ -1625,7 +1641,7 @@ TEST_CASE_METHOD(TApp, "SubcommandAliasIgnoreCaseUnderscore", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "OptionGroupAlias", "[subcom]") {
     double val{0.0};
-    auto sub = app.add_option_group("sub1");
+    auto *sub = app.add_option_group("sub1");
     sub->alias("sub2");
     sub->alias("sub3");
     sub->add_option("-v,--value", val);
@@ -1647,7 +1663,7 @@ TEST_CASE_METHOD(TApp, "OptionGroupAlias", "[subcom]") {
 
 TEST_CASE_METHOD(TApp, "OptionGroupAliasWithSpaces", "[subcom]") {
     double val{0.0};
-    auto sub = app.add_option_group("sub1");
+    auto *sub = app.add_option_group("sub1");
     sub->alias("sub2 bb");
     sub->alias("sub3/b");
     sub->add_option("-v,--value", val);
@@ -1668,7 +1684,7 @@ TEST_CASE_METHOD(TApp, "OptionGroupAliasWithSpaces", "[subcom]") {
 }
 
 TEST_CASE_METHOD(TApp, "subcommand_help", "[subcom]") {
-    auto sub1 = app.add_subcommand("help")->silent();
+    auto *sub1 = app.add_subcommand("help")->silent();
     bool flag{false};
     app.add_flag("--one", flag, "FLAGGER");
     sub1->parse_complete_callback([]() { throw CLI::CallForHelp(); });
@@ -1685,8 +1701,8 @@ TEST_CASE_METHOD(TApp, "subcommand_help", "[subcom]") {
 }
 
 TEST_CASE_METHOD(TApp, "AliasErrors", "[subcom]") {
-    auto sub1 = app.add_subcommand("sub1");
-    auto sub2 = app.add_subcommand("sub2");
+    auto *sub1 = app.add_subcommand("sub1");
+    auto *sub2 = app.add_subcommand("sub2");
 
     CHECK_THROWS_AS(sub2->alias("this is a not\n a valid alias"), CLI::IncorrectConstruction);
     CHECK_NOTHROW(sub2->alias("-alias"));  // this is allowed but would be unusable on command line parsers
@@ -1743,9 +1759,9 @@ TEST_CASE_METHOD(TApp, "ExistingSubcommandMatch", "[subcom]") {
 }
 
 TEST_CASE_METHOD(TApp, "AliasErrorsInOptionGroup", "[subcom]") {
-    auto sub1 = app.add_subcommand("sub1");
-    auto g2 = app.add_option_group("g1");
-    auto sub2 = g2->add_subcommand("sub2");
+    auto *sub1 = app.add_subcommand("sub1");
+    auto *g2 = app.add_option_group("g1");
+    auto *sub2 = g2->add_subcommand("sub2");
 
     // cannot alias to an existing subcommand even if it is in an option group
     CHECK_THROWS_AS(sub2->alias("sub1"), CLI::OptionAlreadyAdded);
@@ -1765,7 +1781,7 @@ TEST_CASE("SharedSubTests: SharedSubcommand", "[subcom]") {
     CLI::App app1{"test program1"};
 
     app1.add_option("-t", val2);
-    auto sub = app1.add_subcommand("", "empty name");
+    auto *sub = app1.add_subcommand("", "empty name");
     sub->add_option("-v,--value", val);
     sub->add_option("-g", val4);
     CLI::App app2{"test program2"};
@@ -1795,7 +1811,7 @@ TEST_CASE("SharedSubTests: SharedSubIndependent", "[subcom]") {
     CLI::App_p app1 = std::make_shared<CLI::App>("test program1");
     app1->allow_extras();
     app1->add_option("-t", val2);
-    auto sub = app1->add_subcommand("", "empty name");
+    auto *sub = app1->add_subcommand("", "empty name");
     sub->add_option("-v,--value", val);
     sub->add_option("-g", val4);
 
@@ -1823,7 +1839,7 @@ TEST_CASE("SharedSubTests: SharedSubIndependentReuse", "[subcom]") {
     CLI::App_p app1 = std::make_shared<CLI::App>("test program1");
     app1->allow_extras();
     app1->add_option("-t", val2);
-    auto sub = app1->add_subcommand("", "empty name");
+    auto *sub = app1->add_subcommand("", "empty name");
     sub->add_option("-v,--value", val);
     sub->add_option("-g", val4);
 
@@ -1884,14 +1900,14 @@ TEST_CASE_METHOD(ManySubcommands, "defaultEnabledSubcommand", "[subcom]") {
     sub2->enabled_by_default();
     run();
     auto rem = app.remaining();
-    CHECK(0u == rem.size());
+    CHECK(rem.empty());
     CHECK(sub2->get_enabled_by_default());
     sub2->disabled();
     CHECK(sub2->get_disabled());
     run();
     // this should disable it again even though it was disabled
     rem = app.remaining();
-    CHECK(0u == rem.size());
+    CHECK(rem.empty());
     CHECK(sub2->get_enabled_by_default());
     CHECK(!sub2->get_disabled());
 }
@@ -1955,3 +1971,16 @@ TEST_CASE_METHOD(TApp, "MultiFinalCallbackCounts", "[subcom]") {
         CHECK(subsub_final == 1);
     }
 }
+
+// From gitter issue
+TEST_CASE_METHOD(TApp, "SubcommandInOptionGroupCallbackCount", "[subcom]") {
+
+    int subcount{0};
+    auto *group1 = app.add_option_group("FirstGroup");
+
+    group1->add_subcommand("g1c1")->callback([&subcount]() { ++subcount; });
+
+    args = {"g1c1"};
+    run();
+    CHECK(subcount == 1);
+}
diff --git a/tests/TimerTest.cpp b/tests/TimerTest.cpp
index 11e6e12..625318a 100644
--- a/tests/TimerTest.cpp
+++ b/tests/TimerTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/tests/TransformTest.cpp b/tests/TransformTest.cpp
index 76a1eff..9406e02 100644
--- a/tests/TransformTest.cpp
+++ b/tests/TransformTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
@@ -6,6 +6,8 @@
 
 #include "app_helper.hpp"
 
+#include <cmath>
+
 #include <array>
 #include <chrono>
 #include <cstdint>
@@ -22,7 +24,7 @@
 
 TEST_CASE_METHOD(TApp, "SimpleTransform", "[transform]") {
     int value{0};
-    auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", std::string("1")}}));
+    auto *opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", std::string("1")}}));
     args = {"-s", "one"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -32,7 +34,7 @@ TEST_CASE_METHOD(TApp, "SimpleTransform", "[transform]") {
 
 TEST_CASE_METHOD(TApp, "SimpleTransformInitList", "[transform]") {
     int value{0};
-    auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", "1"}}));
+    auto *opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", "1"}}));
     args = {"-s", "one"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -42,7 +44,7 @@ TEST_CASE_METHOD(TApp, "SimpleTransformInitList", "[transform]") {
 
 TEST_CASE_METHOD(TApp, "SimpleNumericalTransform", "[transform]") {
     int value{0};
-    auto opt = app.add_option("-s", value)->transform(CLI::Transformer(CLI::TransformPairs<int>{{"one", 1}}));
+    auto *opt = app.add_option("-s", value)->transform(CLI::Transformer(CLI::TransformPairs<int>{{"one", 1}}));
     args = {"-s", "one"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -53,9 +55,9 @@ TEST_CASE_METHOD(TApp, "SimpleNumericalTransform", "[transform]") {
 TEST_CASE_METHOD(TApp, "EnumTransform", "[transform]") {
     enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17 };
     test_cli value{test_cli::val2};
-    auto opt = app.add_option("-s", value)
-                   ->transform(CLI::Transformer(CLI::TransformPairs<test_cli>{
-                       {"val1", test_cli::val1}, {"val2", test_cli::val2}, {"val3", test_cli::val3}}));
+    auto *opt = app.add_option("-s", value)
+                    ->transform(CLI::Transformer(CLI::TransformPairs<test_cli>{
+                        {"val1", test_cli::val1}, {"val2", test_cli::val2}, {"val3", test_cli::val3}}));
     args = {"-s", "val1"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -82,9 +84,9 @@ TEST_CASE_METHOD(TApp, "EnumTransform", "[transform]") {
 TEST_CASE_METHOD(TApp, "EnumCheckedTransform", "[transform]") {
     enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17 };
     test_cli value{test_cli::val1};
-    auto opt = app.add_option("-s", value)
-                   ->transform(CLI::CheckedTransformer(CLI::TransformPairs<test_cli>{
-                       {"val1", test_cli::val1}, {"val2", test_cli::val2}, {"val3", test_cli::val3}}));
+    auto *opt = app.add_option("-s", value)
+                    ->transform(CLI::CheckedTransformer(CLI::TransformPairs<test_cli>{
+                        {"val1", test_cli::val1}, {"val2", test_cli::val2}, {"val3", test_cli::val3}}));
     args = {"-s", "val1"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -149,7 +151,7 @@ TEST_CASE_METHOD(TApp, "EnumCheckedDefaultTransformCallback", "[transform]") {
 
 TEST_CASE_METHOD(TApp, "SimpleTransformFn", "[transform]") {
     int value{0};
-    auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", "1"}}, CLI::ignore_case));
+    auto *opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", "1"}}, CLI::ignore_case));
     args = {"-s", "ONE"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -172,7 +174,7 @@ TEST_CASE_METHOD(TApp, "StringViewTransformFn", "[transform]") {
 
 TEST_CASE_METHOD(TApp, "SimpleNumericalTransformFn", "[transform]") {
     int value{0};
-    auto opt =
+    auto *opt =
         app.add_option("-s", value)
             ->transform(CLI::Transformer(std::vector<std::pair<std::string, int>>{{"one", 1}}, CLI::ignore_case));
     args = {"-s", "ONe"};
@@ -185,7 +187,7 @@ TEST_CASE_METHOD(TApp, "SimpleNumericalTransformFn", "[transform]") {
 TEST_CASE_METHOD(TApp, "SimpleNumericalTransformFnVector", "[transform]") {
     std::vector<std::pair<std::string, int>> conversions{{"one", 1}, {"two", 2}};
     int value{0};
-    auto opt = app.add_option("-s", value)->transform(CLI::Transformer(conversions, CLI::ignore_case));
+    auto *opt = app.add_option("-s", value)->transform(CLI::Transformer(conversions, CLI::ignore_case));
     args = {"-s", "ONe"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -199,7 +201,7 @@ TEST_CASE_METHOD(TApp, "SimpleNumericalTransformFnArray", "[transform]") {
     conversions[1] = std::make_pair(std::string("two"), 2);
 
     int value{0};
-    auto opt = app.add_option("-s", value)->transform(CLI::Transformer(conversions, CLI::ignore_case));
+    auto *opt = app.add_option("-s", value)->transform(CLI::Transformer(conversions, CLI::ignore_case));
     args = {"-s", "ONe"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -215,7 +217,7 @@ TEST_CASE_METHOD(TApp, "SimpleNumericalTransformFnconstexprArray", "[transform]"
     constexpr std::array<std::pair<const char *, int>, 2> conversions_c{{p1, p2}};
 
     int value{0};
-    auto opt = app.add_option("-s", value)->transform(CLI::Transformer(&conversions_c, CLI::ignore_case));
+    auto *opt = app.add_option("-s", value)->transform(CLI::Transformer(&conversions_c, CLI::ignore_case));
     args = {"-s", "ONe"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -233,12 +235,12 @@ TEST_CASE_METHOD(TApp, "SimpleNumericalTransformFnconstexprArray", "[transform]"
 TEST_CASE_METHOD(TApp, "EnumTransformFn", "[transform]") {
     enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17 };
     test_cli value{test_cli::val2};
-    auto opt = app.add_option("-s", value)
-                   ->transform(CLI::Transformer(CLI::TransformPairs<test_cli>{{"val1", test_cli::val1},
-                                                                              {"val2", test_cli::val2},
-                                                                              {"val3", test_cli::val3}},
-                                                CLI::ignore_case,
-                                                CLI::ignore_underscore));
+    auto *opt = app.add_option("-s", value)
+                    ->transform(CLI::Transformer(CLI::TransformPairs<test_cli>{{"val1", test_cli::val1},
+                                                                               {"val2", test_cli::val2},
+                                                                               {"val3", test_cli::val3}},
+                                                 CLI::ignore_case,
+                                                 CLI::ignore_underscore));
     args = {"-s", "val_1"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -261,7 +263,7 @@ TEST_CASE_METHOD(TApp, "EnumTransformFnMap", "[transform]") {
     enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17 };
     std::map<std::string, test_cli> map{{"val1", test_cli::val1}, {"val2", test_cli::val2}, {"val3", test_cli::val3}};
     test_cli value{test_cli::val3};
-    auto opt = app.add_option("-s", value)->transform(CLI::Transformer(map, CLI::ignore_case, CLI::ignore_underscore));
+    auto *opt = app.add_option("-s", value)->transform(CLI::Transformer(map, CLI::ignore_case, CLI::ignore_underscore));
     args = {"-s", "val_1"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -284,7 +286,8 @@ TEST_CASE_METHOD(TApp, "EnumTransformFnPtrMap", "[transform]") {
     enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17, val4 = 37 };
     std::map<std::string, test_cli> map{{"val1", test_cli::val1}, {"val2", test_cli::val2}, {"val3", test_cli::val3}};
     test_cli value{test_cli::val2};
-    auto opt = app.add_option("-s", value)->transform(CLI::Transformer(&map, CLI::ignore_case, CLI::ignore_underscore));
+    auto *opt =
+        app.add_option("-s", value)->transform(CLI::Transformer(&map, CLI::ignore_case, CLI::ignore_underscore));
     args = {"-s", "val_1"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -316,7 +319,7 @@ TEST_CASE_METHOD(TApp, "EnumTransformFnSharedPtrMap", "[transform]") {
     mp["val3"] = test_cli::val3;
 
     test_cli value{test_cli::val2};
-    auto opt = app.add_option("-s", value)->transform(CLI::Transformer(map, CLI::ignore_case, CLI::ignore_underscore));
+    auto *opt = app.add_option("-s", value)->transform(CLI::Transformer(map, CLI::ignore_case, CLI::ignore_underscore));
     args = {"-s", "val_1"};
     run();
     CHECK(app.count("-s") == 1u);
@@ -343,7 +346,7 @@ TEST_CASE_METHOD(TApp, "EnumTransformFnSharedPtrMap", "[transform]") {
 TEST_CASE_METHOD(TApp, "TransformCascade", "[transform]") {
 
     std::string output;
-    auto opt = app.add_option("-s", output);
+    auto *opt = app.add_option("-s", output);
     opt->transform(CLI::Transformer({{"abc", "abcd"}, {"bbc", "bbcd"}, {"cbc", "cbcd"}}, CLI::ignore_case));
     opt->transform(
         CLI::Transformer({{"ab", "abc"}, {"bc", "bbc"}, {"cb", "cbc"}}, CLI::ignore_case, CLI::ignore_underscore));
@@ -370,7 +373,7 @@ TEST_CASE_METHOD(TApp, "TransformCascade", "[transform]") {
 TEST_CASE_METHOD(TApp, "TransformCascadeDeactivate", "[transform]") {
 
     std::string output;
-    auto opt = app.add_option("-s", output);
+    auto *opt = app.add_option("-s", output);
     opt->transform(
         CLI::Transformer({{"abc", "abcd"}, {"bbc", "bbcd"}, {"cbc", "cbcd"}}, CLI::ignore_case).name("tform1"));
     opt->transform(
@@ -390,7 +393,7 @@ TEST_CASE_METHOD(TApp, "TransformCascadeDeactivate", "[transform]") {
     args = {"-s", "C_B"};
     CHECK_THROWS_AS(run(), CLI::ValidationError);
 
-    auto validator = opt->get_validator("tform2");
+    auto *validator = opt->get_validator("tform2");
     CHECK(!validator->get_active());
     CHECK("tform2" == validator->get_name());
     validator->active();
@@ -475,28 +478,28 @@ TEST_CASE_METHOD(TApp, "IntTransformNonMerge", "[transform]") {
     CHECK(help.find("15->5") != std::string::npos);
     CHECK(help.find("25->5") != std::string::npos);
 
-    auto validator = app.get_option("-s")->get_validator();
+    auto *validator = app.get_option("-s")->get_validator();
     help = validator->get_description();
     CHECK(help.find("15->5") != std::string::npos);
     CHECK(help.find("25->5") != std::string::npos);
 
-    auto validator2 = app.get_option("-s")->get_validator("merge");
+    auto *validator2 = app.get_option("-s")->get_validator("merge");
     CHECK(validator == validator2);
 }
 
 TEST_CASE_METHOD(TApp, "IntTransformMergeWithCustomValidator", "[transform]") {
     std::string value;
-    auto opt = app.add_option("-s", value)
-                   ->transform(CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}) |
-                                   CLI::Validator(
-                                       [](std::string &element) {
-                                           if(element == "frog") {
-                                               element = "hops";
-                                           }
-                                           return std::string{};
-                                       },
-                                       std::string{}),
-                               "check");
+    auto *opt = app.add_option("-s", value)
+                    ->transform(CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}) |
+                                    CLI::Validator(
+                                        [](std::string &element) {
+                                            if(element == "frog") {
+                                                element = "hops";
+                                            }
+                                            return std::string{};
+                                        },
+                                        std::string{}),
+                                "check");
     args = {"-s", "15"};
     run();
     CHECK("5" == value);
@@ -518,7 +521,7 @@ TEST_CASE_METHOD(TApp, "IntTransformMergeWithCustomValidator", "[transform]") {
     CHECK(help.find("15->5") != std::string::npos);
     CHECK(help.find("OR") == std::string::npos);
 
-    auto validator = opt->get_validator("check");
+    auto *validator = opt->get_validator("check");
     CHECK("check" == validator->get_name());
     validator->active(false);
     help = app.help();
@@ -526,7 +529,7 @@ TEST_CASE_METHOD(TApp, "IntTransformMergeWithCustomValidator", "[transform]") {
 }
 
 TEST_CASE_METHOD(TApp, "BoundTests", "[transform]") {
-    double value;
+    double value = NAN;
     app.add_option("-s", value)->transform(CLI::Bound(3.4, 5.9));
     args = {"-s", "15"};
     run();
@@ -636,9 +639,8 @@ TEST_CASE_METHOD(TApp, "NumberWithUnitMandatoryUnit", "[transform]") {
 
     int value{0};
     app.add_option("-n", value)
-        ->transform(CLI::AsNumberWithUnit(mapping,
-                                          CLI::AsNumberWithUnit::Options(CLI::AsNumberWithUnit::UNIT_REQUIRED |
-                                                                         CLI::AsNumberWithUnit::CASE_SENSITIVE)));
+        ->transform(CLI::AsNumberWithUnit(
+            mapping, CLI::AsNumberWithUnit::UNIT_REQUIRED | CLI::AsNumberWithUnit::CASE_SENSITIVE));
 
     args = {"-n", "42a"};
     run();
@@ -657,9 +659,8 @@ TEST_CASE_METHOD(TApp, "NumberWithUnitMandatoryUnit2", "[transform]") {
 
     int value{0};
     app.add_option("-n", value)
-        ->transform(CLI::AsNumberWithUnit(mapping,
-                                          CLI::AsNumberWithUnit::Options(CLI::AsNumberWithUnit::UNIT_REQUIRED |
-                                                                         CLI::AsNumberWithUnit::CASE_INSENSITIVE)));
+        ->transform(CLI::AsNumberWithUnit(
+            mapping, CLI::AsNumberWithUnit::UNIT_REQUIRED | CLI::AsNumberWithUnit::CASE_INSENSITIVE));
 
     args = {"-n", "42A"};
     run();
@@ -708,7 +709,7 @@ TEST_CASE_METHOD(TApp, "NumberWithUnitBadInput", "[transform]") {
 TEST_CASE_METHOD(TApp, "NumberWithUnitIntOverflow", "[transform]") {
     std::map<std::string, int> mapping{{"a", 1000000}, {"b", 100}, {"c", 101}};
 
-    std::int32_t value;
+    std::int32_t value = 0;
     app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
 
     args = {"-n", "1000 a"};
diff --git a/tests/TrueFalseTest.cpp b/tests/TrueFalseTest.cpp
index c3b556c..93f2f3f 100644
--- a/tests/TrueFalseTest.cpp
+++ b/tests/TrueFalseTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/tests/WindowsTest.cpp b/tests/WindowsTest.cpp
index 5a75908..a17d587 100644
--- a/tests/WindowsTest.cpp
+++ b/tests/WindowsTest.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/tests/app_helper.hpp b/tests/app_helper.hpp
index 9199168..db98b29 100644
--- a/tests/app_helper.hpp
+++ b/tests/app_helper.hpp
@@ -33,6 +33,8 @@ class TApp {
     }
 };
 
+CLI11_INLINE int fileClear(const std::string &name) { return std::remove(name.c_str()); }
+
 class TempFile {
     std::string _name{};
 
@@ -46,8 +48,8 @@ class TempFile {
         std::remove(_name.c_str());  // Doesn't matter if returns 0 or not
     }
 
-    operator const std::string &() const { return _name; }
-    const char *c_str() const { return _name.c_str(); }
+    operator const std::string &() const { return _name; }  // NOLINT(google-explicit-constructor)
+    CLI11_NODISCARD const char *c_str() const { return _name.c_str(); }
 };
 
 inline void put_env(std::string name, std::string value) {
diff --git a/tests/catch.hpp b/tests/catch.hpp
index 2aaeae7..c1778f5 100644
--- a/tests/catch.hpp
+++ b/tests/catch.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/tests/informational.cpp b/tests/informational.cpp
index f0fc70b..4f7f27b 100644
--- a/tests/informational.cpp
+++ b/tests/informational.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/tests/link_test_1.cpp b/tests/link_test_1.cpp
index 447afbf..ba1b2d8 100644
--- a/tests/link_test_1.cpp
+++ b/tests/link_test_1.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/tests/link_test_2.cpp b/tests/link_test_2.cpp
index abce549..46d77be 100644
--- a/tests/link_test_2.cpp
+++ b/tests/link_test_2.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/tests/main.cpp b/tests/main.cpp
index f5fbebd..451f650 100644
--- a/tests/main.cpp
+++ b/tests/main.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/tests/meson.build b/tests/meson.build
new file mode 100644
index 0000000..27e2216
--- /dev/null
+++ b/tests/meson.build
@@ -0,0 +1,78 @@
+catch2 = dependency('catch2')
+
+testmain = static_library(
+    'catch_main',
+    'main.cpp', 'catch.hpp',
+    dependencies: catch2,
+)
+testdep = declare_dependency(
+    link_with: testmain,
+    dependencies: [catch2, CLI11_dep]
+)
+
+link_test_lib = library(
+    'link_test_1',
+    'link_test_1.cpp',
+    dependencies: CLI11_dep,
+)
+
+if cxx.get_id() == 'msvc'
+    nodeprecated = ['/wd4996']
+else
+    nodeprecated = ['-Wno-deprecated-declarations']
+endif
+
+boost = dependency('boost', required: false)
+if boost.found()
+    boost_dep = declare_dependency(
+        dependencies: boost,
+        compile_args: '-DCLI11_BOOST_OPTIONAL',
+    )
+else
+    boost_dep = declare_dependency()
+endif
+
+testnames = [
+    ['HelpersTest', {}],
+    ['ConfigFileTest', {}],
+    ['OptionTypeTest', {}],
+    ['SimpleTest', {}],
+    ['AppTest', {}],
+    ['SetTest', {}],
+    ['TransformTest', {}],
+    ['CreationTest', {}],
+    ['SubcommandTest', {}],
+    ['HelpTest', {}],
+    ['FormatterTest', {}],
+    ['NewParseTest', {}],
+    ['OptionalTest', {'dependencies': boost_dep}],
+    ['DeprecatedTest', {'cpp_args': nodeprecated}],
+    ['StringParseTest', {}],
+    ['ComplexTypeTest', {}],
+    ['TrueFalseTest', {}],
+    ['OptionGroupTest', {}],
+    # multi-only
+    ['TimerTest', {}],
+    # link_test
+    ['link_test_2', {'link_with': link_test_lib}],
+]
+
+if host_machine.system() == 'windows'
+    testnames += [['WindowsTest', {}]]
+endif
+
+if boost.found()
+    testnames += [['BoostOptionTypeTest', {'dependencies': boost_dep}]]
+endif
+
+foreach n: testnames
+    name = n[0]
+    kwargs = n[1]
+    t = executable(name, name + '.cpp',
+        cpp_args: kwargs.get('cpp_args', []),
+        build_by_default: false,
+        dependencies: [testdep] + kwargs.get('dependencies', []),
+        link_with: kwargs.get('link_with', [])
+    )
+    test(name, t)
+endforeach
diff --git a/tests/mesonTest/main.cpp b/tests/mesonTest/main.cpp
index 64d45eb..39bb784 100644
--- a/tests/mesonTest/main.cpp
+++ b/tests/mesonTest/main.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
 // under NSF AWARD 1414736 and by the respective contributors.
 // All rights reserved.
 //
diff --git a/tests/mesonTest/subprojects/CLI11 b/tests/mesonTest/subprojects/CLI11
deleted file mode 120000
index a8a4f8c..0000000
--- a/tests/mesonTest/subprojects/CLI11
+++ /dev/null
@@ -1 +0,0 @@
-../../..
\ No newline at end of file

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/include/CLI/impl/App_inl.hpp
-rw-r--r--  root/root   /usr/include/CLI/impl/Config_inl.hpp
-rw-r--r--  root/root   /usr/include/CLI/impl/Formatter_inl.hpp
-rw-r--r--  root/root   /usr/include/CLI/impl/Option_inl.hpp
-rw-r--r--  root/root   /usr/include/CLI/impl/Split_inl.hpp
-rw-r--r--  root/root   /usr/include/CLI/impl/StringTools_inl.hpp
-rw-r--r--  root/root   /usr/include/CLI/impl/Validators_inl.hpp
-rw-r--r--  root/root   /usr/share/doc/libcli11-dev/examples/testEXE.cpp
-rw-r--r--  root/root   /usr/share/doc/libcli11-dev/html/classCLI_1_1FileOnDefaultPath-members.html
-rw-r--r--  root/root   /usr/share/doc/libcli11-dev/html/classCLI_1_1FileOnDefaultPath.html
-rw-r--r--  root/root   /usr/share/doc/libcli11-dev/html/classCLI_1_1FileOnDefaultPath.png

No differences were encountered between the control files of package libcli11-dev

No differences were encountered between the control files of package libcli11-doc

More details

Full run details