From cef7d68f5be4c59fb1393e83a54af15ffb645b4d Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Thu, 25 Sep 2025 19:40:33 +0200 Subject: [PATCH 01/14] Add Converter class template --- include/argparse.hpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/include/argparse.hpp b/include/argparse.hpp index 96374c2..4b01abe 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -99,6 +99,32 @@ namespace argparse return static_cast(lhs) & static_cast(rhs); } + template + class Converter + { + public: + auto from_string(std::string const & s, T & t) const -> bool + { + auto iss = std::istringstream(s); + iss >> t; + + return !iss.fail() && (iss.eof() || iss.peek() == std::istringstream::traits_type::eof()); + } + + auto to_string(T const & t) const -> std::string + { + auto ostr = std::ostringstream(); + ostr << t; + + return ostr.str(); + } + + auto are_equal(T const & lhs, T const & rhs) const -> bool + { + return lhs == rhs; + } + }; + inline auto from_string(std::string const & s, auto & t) -> bool { auto iss = std::istringstream(s); From 139fc10576e01ac6082d257705563642e81b4fd1 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Thu, 25 Sep 2025 19:45:14 +0200 Subject: [PATCH 02/14] Change from_string function to use Converter --- include/argparse.hpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/include/argparse.hpp b/include/argparse.hpp index 4b01abe..c2a28b1 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -125,12 +125,11 @@ namespace argparse } }; - inline auto from_string(std::string const & s, auto & t) -> bool + template + inline auto from_string(std::string const & s, T & t) -> bool { - auto iss = std::istringstream(s); - iss >> t; - - return !iss.fail() && (iss.eof() || iss.peek() == std::istringstream::traits_type::eof()); + auto const conv = Converter(); + return conv.from_string(s, t); } inline auto to_string(auto const & t) -> std::string From bdb1c1c1ee9f9d4ce0350dcee9b69c2cf4fd0f4c Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Thu, 25 Sep 2025 19:45:36 +0200 Subject: [PATCH 03/14] Change to_string function to use Converter --- include/argparse.hpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/include/argparse.hpp b/include/argparse.hpp index c2a28b1..4f8b1f6 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -132,12 +132,11 @@ namespace argparse return conv.from_string(s, t); } - inline auto to_string(auto const & t) -> std::string + template + inline auto to_string(T const & t) -> std::string { - auto ostr = std::ostringstream(); - ostr << t; - - return ostr.str(); + auto const conv = Converter(); + return conv.to_string(t); } inline auto are_equal(auto const & lhs, auto const & rhs) -> bool From 263f69c852d428480bc968e3dc00a338f87bb009 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Thu, 25 Sep 2025 19:45:59 +0200 Subject: [PATCH 04/14] Change are_equal function to use Converter --- include/argparse.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/argparse.hpp b/include/argparse.hpp index 4f8b1f6..b1a2e30 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -139,9 +139,11 @@ namespace argparse return conv.to_string(t); } - inline auto are_equal(auto const & lhs, auto const & rhs) -> bool + template + inline auto are_equal(T const & lhs, T const & rhs) -> bool { - return lhs == rhs; + auto const conv = Converter(); + return conv.are_equal(lhs, rhs); } class ArgumentParser From b4d4ea06aa767e4c140fd2c61f6cbc2f157b267b Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Thu, 25 Sep 2025 20:13:11 +0200 Subject: [PATCH 05/14] Adapt unit tests --- test/unittest/custom_a.h | 36 +++++++++++++---------- test/unittest/custom_b.h | 35 ++++++++++++---------- test/unittest/test_parsing_optional.cpp | 7 +++++ test/unittest/test_parsing_positional.cpp | 7 +++++ 4 files changed, 54 insertions(+), 31 deletions(-) diff --git a/test/unittest/custom_a.h b/test/unittest/custom_a.h index 645a439..600884d 100644 --- a/test/unittest/custom_a.h +++ b/test/unittest/custom_a.h @@ -1,6 +1,7 @@ #ifndef CUSTOMA_H #define CUSTOMA_H +#include "argparse.hpp" #include @@ -20,23 +21,28 @@ namespace foo }; } -namespace foo -{ -inline auto operator==(Custom const & lhs, Custom const & rhs) -> bool -{ - return lhs.m_text == rhs.m_text; -} - -inline auto from_string(std::string const & s, Custom & c) -> bool +namespace argparse { - c = Custom(s); - return true; -} - -inline auto to_string(Custom const& c) -> std::string +template<> +class Converter { - return ""; -} + public: + auto from_string(std::string const & s, foo::Custom & t) const -> bool + { + t = foo::Custom(s); + return true; + } + + auto to_string(foo::Custom const & t) const -> std::string + { + return ""; + } + + auto are_equal(foo::Custom const & lhs, foo::Custom const & rhs) const -> bool + { + return lhs.m_text == rhs.m_text; + } +}; } #endif /* CUSTOMA_H */ diff --git a/test/unittest/custom_b.h b/test/unittest/custom_b.h index 8867757..7e51aef 100644 --- a/test/unittest/custom_b.h +++ b/test/unittest/custom_b.h @@ -1,6 +1,7 @@ #ifndef CUSTOMB_H #define CUSTOMB_H +#include "argparse.hpp" #include @@ -23,23 +24,25 @@ namespace bar namespace argparse { template<> -inline auto from_string(std::string const & s, bar::Custom & c) -> bool +class Converter { - c = bar::Custom(s); - return true; -} - -template<> -inline auto to_string(bar::Custom const & t) -> std::string -{ - return ""; -} - -template<> -inline auto are_equal(bar::Custom const & lhs, bar::Custom const & rhs) -> bool -{ - return lhs.m_text == rhs.m_text; -} + public: + auto from_string(std::string const & s, bar::Custom & t) const -> bool + { + t = bar::Custom(s); + return true; + } + + auto to_string(bar::Custom const & t) const -> std::string + { + return ""; + } + + auto are_equal(bar::Custom const & lhs, bar::Custom const & rhs) const -> bool + { + return lhs.m_text == rhs.m_text; + } +}; } #endif /* CUSTOMB_H */ diff --git a/test/unittest/test_parsing_optional.cpp b/test/unittest/test_parsing_optional.cpp index 19f40da..fa7e701 100644 --- a/test/unittest/test_parsing_optional.cpp +++ b/test/unittest/test_parsing_optional.cpp @@ -12,6 +12,13 @@ using namespace std::string_literals; +namespace foo +{ +inline auto operator==(Custom const & lhs, Custom const & rhs) -> bool +{ + return lhs.m_text == rhs.m_text; +} +} namespace bar { inline auto operator==(Custom const & lhs, Custom const & rhs) -> bool diff --git a/test/unittest/test_parsing_positional.cpp b/test/unittest/test_parsing_positional.cpp index 891aed5..8da177e 100644 --- a/test/unittest/test_parsing_positional.cpp +++ b/test/unittest/test_parsing_positional.cpp @@ -12,6 +12,13 @@ using namespace std::string_literals; +namespace foo +{ +inline auto operator==(Custom const & lhs, Custom const & rhs) -> bool +{ + return lhs.m_text == rhs.m_text; +} +} namespace bar { inline auto operator==(Custom const & lhs, Custom const & rhs) -> bool From 0e183d203dd5ae5b6cb75e4140b0d8667f177245 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Thu, 25 Sep 2025 20:19:43 +0200 Subject: [PATCH 06/14] Use qualified calls --- include/argparse.hpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/include/argparse.hpp b/include/argparse.hpp index b1a2e30..94c5f1d 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -546,9 +546,8 @@ namespace argparse } else { - using argparse::from_string; auto value = T(); - if (from_string(string, value)) + if (argparse::from_string(string, value)) { return std::any(value); } @@ -567,14 +566,13 @@ namespace argparse } else { - using argparse::to_string; - return to_string(std::any_cast(value)); + return argparse::to_string(std::any_cast(value)); } } auto compare(std::any const & lhs, std::any const & rhs) const -> bool override { - return are_equal(std::any_cast(lhs), std::any_cast(rhs)); + return argparse::are_equal(std::any_cast(lhs), std::any_cast(rhs)); } auto transform(std::vector const & values) const -> std::any override From d6b34183ff2bd41de405966a4ea6eef5bf39621c Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Thu, 25 Sep 2025 20:22:53 +0200 Subject: [PATCH 07/14] Adapt code in tutorial's custom1 example --- tutorial/custom1.cpp | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/tutorial/custom1.cpp b/tutorial/custom1.cpp index eec353e..3ce1101 100644 --- a/tutorial/custom1.cpp +++ b/tutorial/custom1.cpp @@ -21,25 +21,27 @@ namespace geometry namespace argparse { template<> -inline auto from_string(std::string const & s, geometry::Point & p) -> bool +class Converter { - std::istringstream iss(s); - char comma; - iss >> p.x >> comma >> p.y; - return true; -} + public: + auto from_string(std::string const & s, geometry::Point & p) const -> bool + { + std::istringstream iss(s); + char comma; + iss >> p.x >> comma >> p.y; + return true; + } -template<> -inline auto to_string(geometry::Point const & p) -> std::string -{ - return std::to_string(p.x) + "," + std::to_string(p.y); -} + auto to_string(geometry::Point const & p) const -> std::string + { + return std::to_string(p.x) + "," + std::to_string(p.y); + } -template<> -inline auto are_equal(geometry::Point const & l, geometry::Point const & r) -> bool -{ - return l.x == r.x && l.y == r.y; -} + auto are_equal(geometry::Point const & l, geometry::Point const & r) const -> bool + { + return l.x == r.x && l.y == r.y; + } +}; } auto main(int argc, char * argv[]) -> int From ed214f453371fe2b37ec1463a72bc898f92ffb52 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Thu, 25 Sep 2025 20:26:04 +0200 Subject: [PATCH 08/14] Adapt code in tutorial's custom2 example --- tutorial/custom2.cpp | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/tutorial/custom2.cpp b/tutorial/custom2.cpp index 613a887..4c1e7c5 100644 --- a/tutorial/custom2.cpp +++ b/tutorial/custom2.cpp @@ -21,25 +21,27 @@ namespace geometry namespace argparse { template<> -inline auto from_string(std::string const & s, geometry::Point & p) -> bool +class Converter { - std::istringstream iss(s); - char comma; - iss >> p.x >> comma >> p.y; - return !iss.fail(); -} + public: + auto from_string(std::string const & s, geometry::Point & p) const -> bool + { + std::istringstream iss(s); + char comma; + iss >> p.x >> comma >> p.y; + return !iss.fail(); + } -template<> -inline auto to_string(geometry::Point const & p) -> std::string -{ - return std::to_string(p.x) + "," + std::to_string(p.y); -} + auto to_string(geometry::Point const & p) const -> std::string + { + return std::to_string(p.x) + "," + std::to_string(p.y); + } -template<> -inline auto are_equal(geometry::Point const & l, geometry::Point const & r) -> bool -{ - return l.x == r.x && l.y == r.y; -} + auto are_equal(geometry::Point const & l, geometry::Point const & r) const -> bool + { + return l.x == r.x && l.y == r.y; + } +}; } auto main(int argc, char * argv[]) -> int From 310c94693dc446aedd9bc6af96199f34c059df18 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Thu, 25 Sep 2025 20:31:25 +0200 Subject: [PATCH 09/14] Adapt code in tutorial's custom3 example --- tutorial/custom3.cpp | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/tutorial/custom3.cpp b/tutorial/custom3.cpp index 005895c..7b98e1d 100644 --- a/tutorial/custom3.cpp +++ b/tutorial/custom3.cpp @@ -21,25 +21,27 @@ namespace geometry namespace argparse { template<> -inline auto from_string(std::string const & s, geometry::Point & p) -> bool +class Converter { - std::istringstream iss(s); - char comma; - iss >> p.x >> comma >> p.y; - return !iss.fail(); -} + public: + auto from_string(std::string const & s, geometry::Point & p) const -> bool + { + std::istringstream iss(s); + char comma; + iss >> p.x >> comma >> p.y; + return !iss.fail(); + } -template<> -inline auto to_string(geometry::Point const & p) -> std::string -{ - return std::to_string(p.x) + "," + std::to_string(p.y); -} + auto to_string(geometry::Point const & p) const -> std::string + { + return std::to_string(p.x) + "," + std::to_string(p.y); + } -template<> -inline auto are_equal(geometry::Point const & l, geometry::Point const & r) -> bool -{ - return l.x == r.x && l.y == r.y; -} + auto are_equal(geometry::Point const & l, geometry::Point const & r) const -> bool + { + return l.x == r.x && l.y == r.y; + } +}; } auto main(int argc, char * argv[]) -> int From ff3ef15736fbc5ac9e12df9b26670d1478a401bb Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Thu, 25 Sep 2025 20:36:46 +0200 Subject: [PATCH 10/14] Update tutorial --- tutorial/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial/readme.md b/tutorial/readme.md index 093e70d..3502986 100644 --- a/tutorial/readme.md +++ b/tutorial/readme.md @@ -930,7 +930,7 @@ The library could act smart here and automatically convert the `const char*` typ You may wonder whether this library allows using types other than the built-in ones (`int`, `float`, `double`, etc.) or `std::string`. Actually, yes, it does! -You can parse directly to any custom type, provided that this type is default-constructible and you provide a way to do string-type and type-string conversion as well as equality comparison. This may sound complicated, but basically boils down to specialising three template functions: `argparse::from_string`, `argparse::to_string`, and `argparse::are_equal`, which are the library's customisation points. Let's have a look at an example (`custom1.cpp`): +You can parse directly to any custom type, provided that this type is default-constructible and you provide a way to do string-type and type-string conversion as well as equality comparison. This may sound complicated, but basically boils down to specialising the `argparse::Converter` template class, which is the library's customisation point. Let's have a look at an example (`custom1.cpp`): ```c++ #include "argparse.hpp" #include @@ -1003,7 +1003,7 @@ optional arguments: $ custom1 0,0 1,1 The distance is 1.41421 ``` -The return value of `argparse::from_string` indicates whether the conversion succeeded. You can use it to your advantage (`custom2.cpp`): +The return value of `argparse::Converter::from_string` indicates whether the conversion succeeded. You can use it to your advantage (`custom2.cpp`): ```c++ #include "argparse.hpp" #include From 5e02c66838ae551ddfc92531e3616b3b9e60c3a9 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Thu, 25 Sep 2025 20:38:06 +0200 Subject: [PATCH 11/14] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c0c9d73..bfc693b 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ The below lists features of the `argparse` module that this implementation suppo * [x] `'+'` (as `one_or_more`) * [x] `const` (renamed to `const_` due to keyword clash) * [x] `default` (renamed to `default_` due to keyword clash; only for optional arguments and with no string parsing) - * [x] `type` (built-in (except for `bool`) and user-defined types (via overloading `from_string` function)) + * [x] `type` (built-in (except for `bool`) and user-defined types (via specialising `argparse::Converter` class template)) * [x] `choices` * [x] `required` * [x] `help` From d96307504a92b331aaecd22a6d631950ec4646f7 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Thu, 25 Sep 2025 21:04:17 +0200 Subject: [PATCH 12/14] Update tutorial --- tutorial/readme.md | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/tutorial/readme.md b/tutorial/readme.md index 3502986..34487ee 100644 --- a/tutorial/readme.md +++ b/tutorial/readme.md @@ -955,25 +955,27 @@ namespace geometry namespace argparse { template<> -inline auto from_string(std::string const & s, geometry::Point & p) -> bool +class Converter { - std::istringstream iss(s); - char comma; - iss >> p.x >> comma >> p.y; - return true; -} + public: + auto from_string(std::string const & s, geometry::Point & p) const -> bool + { + std::istringstream iss(s); + char comma; + iss >> p.x >> comma >> p.y; + return true; + } -template<> -inline auto to_string(geometry::Point const & p) -> std::string -{ - return std::to_string(p.x) + "," + std::to_string(p.y); -} + auto to_string(geometry::Point const & p) const -> std::string + { + return std::to_string(p.x) + "," + std::to_string(p.y); + } -template<> -inline auto are_equal(geometry::Point const & l, geometry::Point const & r) -> bool -{ - return l.x == r.x && l.y == r.y; -} + auto are_equal(geometry::Point const & l, geometry::Point const & r) const -> bool + { + return l.x == r.x && l.y == r.y; + } +}; } auto main(int argc, char * argv[]) -> int From 41d146204e2e0f4279c862f686c2035e844d00c6 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Thu, 25 Sep 2025 21:05:07 +0200 Subject: [PATCH 13/14] Update tutorial --- tutorial/readme.md | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/tutorial/readme.md b/tutorial/readme.md index 34487ee..7168b15 100644 --- a/tutorial/readme.md +++ b/tutorial/readme.md @@ -1030,25 +1030,27 @@ namespace geometry namespace argparse { template<> -inline auto from_string(std::string const & s, geometry::Point & p) -> bool +class Converter { - std::istringstream iss(s); - char comma; - iss >> p.x >> comma >> p.y; - return !iss.fail(); -} + public: + auto from_string(std::string const & s, geometry::Point & p) const -> bool + { + std::istringstream iss(s); + char comma; + iss >> p.x >> comma >> p.y; + return !iss.fail(); + } -template<> -inline auto to_string(geometry::Point const & p) -> std::string -{ - return std::to_string(p.x) + "," + std::to_string(p.y); -} + auto to_string(geometry::Point const & p) const -> std::string + { + return std::to_string(p.x) + "," + std::to_string(p.y); + } -template<> -inline auto are_equal(geometry::Point const & l, geometry::Point const & r) -> bool -{ - return l.x == r.x && l.y == r.y; -} + auto are_equal(geometry::Point const & l, geometry::Point const & r) const -> bool + { + return l.x == r.x && l.y == r.y; + } +}; } auto main(int argc, char * argv[]) -> int From db2de8e880110443b7aec2931a91cd5ef2b66ae5 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Thu, 25 Sep 2025 21:05:47 +0200 Subject: [PATCH 14/14] Update tutorial --- tutorial/readme.md | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/tutorial/readme.md b/tutorial/readme.md index 7168b15..49bc635 100644 --- a/tutorial/readme.md +++ b/tutorial/readme.md @@ -1107,25 +1107,27 @@ namespace geometry namespace argparse { template<> -inline auto from_string(std::string const & s, geometry::Point & p) -> bool +class Converter { - std::istringstream iss(s); - char comma; - iss >> p.x >> comma >> p.y; - return !iss.fail(); -} + public: + auto from_string(std::string const & s, geometry::Point & p) const -> bool + { + std::istringstream iss(s); + char comma; + iss >> p.x >> comma >> p.y; + return !iss.fail(); + } -template<> -inline auto to_string(geometry::Point const & p) -> std::string -{ - return std::to_string(p.x) + "," + std::to_string(p.y); -} + auto to_string(geometry::Point const & p) const -> std::string + { + return std::to_string(p.x) + "," + std::to_string(p.y); + } -template<> -inline auto are_equal(geometry::Point const & l, geometry::Point const & r) -> bool -{ - return l.x == r.x && l.y == r.y; -} + auto are_equal(geometry::Point const & l, geometry::Point const & r) const -> bool + { + return l.x == r.x && l.y == r.y; + } +}; } auto main(int argc, char * argv[]) -> int