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` diff --git a/include/argparse.hpp b/include/argparse.hpp index 96374c2..94c5f1d 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -99,25 +99,51 @@ namespace argparse return static_cast(lhs) & static_cast(rhs); } - inline auto from_string(std::string const & s, auto & t) -> bool + template + class Converter { - auto iss = std::istringstream(s); - iss >> t; + 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()); - } + 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(); + } - inline auto to_string(auto const & t) -> std::string + auto are_equal(T const & lhs, T const & rhs) const -> bool + { + return lhs == rhs; + } + }; + + template + inline auto from_string(std::string const & s, T & t) -> bool { - auto ostr = std::ostringstream(); - ostr << t; + auto const conv = Converter(); + return conv.from_string(s, t); + } - return ostr.str(); + template + inline auto to_string(T const & t) -> std::string + { + auto const conv = Converter(); + 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 @@ -520,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); } @@ -541,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 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 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 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 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 diff --git a/tutorial/readme.md b/tutorial/readme.md index 093e70d..49bc635 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 @@ -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 @@ -1003,7 +1005,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 @@ -1028,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 @@ -1103,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