From ca07246ade007b094a5a3f6540ac1605133a72e8 Mon Sep 17 00:00:00 2001 From: Ed Catmur Date: Wed, 22 Feb 2023 14:18:21 +0000 Subject: [PATCH 1/2] Customization point for to-string conversion The use of boost::lexical_cast for default_value and implicit_value has several deficiencies; firstly, with the adoption of std::format in C++20 it is becoming more and more usual for libraries to regard iostreams formatting as deprecated and not provide ostream operator<< overloads for classes. Secondly, the standard library has added ostreams formatting for classes (esp. std::chrono) at different times, making it confusing when code works on one compiler and not another; it is possible to always use the two-argument overloads of default_value and implicit_value, but this is extra repeated work for the user and encourages providing string literals which may get out of sync with the actually provided default/implicit value. Finally, as a general point, when validate() is customized it may use a different format than iostreams (often terser and avoiding characters that are difficult to supply on the command line). In conclusion, it would be useful to lexical_cast to std::string indirectly, via a customization point that can be customized by the user specifically for Boost.ProgramOptions. This PR supplies a customization point in boost::program_options namespace with primary overload: template std::string make_textual(const T& v, typed_value*, long) { return boost::lexical_cast(v); } The first parameter is the value being textualized (by default_value or implicit_value single-argument overloads). The second parameter is mainly for ADL and disambiguation, but also if non-null it provides access to the 'typed_value' being modified. The third parameter is for overload resolution, as with 'validate'. There is one overload provided, for std::vector: template std::string make_textual(const std::vector& v, typed_value, charT>*, long) { std::string textual; for (unsigned i = 0; i < v.size(); ++i) { if (i != 0) textual += ' '; textual += make_textual(v[i], (typed_value*)0, 0); } return textual; } This mirrors the overload of 'validate' for std::vector, since it would be surprising for 'make_textual' to work where 'validate' does. It also illustrates how to recursively invoke 'make_textual', and that the 'typed_value' parameter may be null. This is just conceivably a breaking change if some user has provided operator<< for std::vector but not for T for some type T, but this seems very unlikely. --- .../program_options/detail/value_semantic.hpp | 34 +++++++++++++++++++ .../boost/program_options/value_semantic.hpp | 26 ++++++-------- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/include/boost/program_options/detail/value_semantic.hpp b/include/boost/program_options/detail/value_semantic.hpp index 9531339a34..5aef0802ff 100644 --- a/include/boost/program_options/detail/value_semantic.hpp +++ b/include/boost/program_options/detail/value_semantic.hpp @@ -6,6 +6,7 @@ // This file defines template functions that are declared in // ../value_semantic.hpp. +#include #include // forward declaration @@ -46,6 +47,39 @@ namespace boost { namespace program_options { } } + template + std::string make_textual(const T& v, typed_value*, long) + { + return boost::lexical_cast(v); + } + + template + std::string make_textual(const std::vector& v, typed_value, charT>*, long) + { + std::string textual; + for (unsigned i = 0; i < v.size(); ++i) + { + if (i != 0) + textual += ' '; + textual += make_textual(v[i], (typed_value*)0, 0); + } + return textual; + } + + template + typed_value* + typed_value::default_value(const T& v) + { + return default_value(v, make_textual(v, this, 0)); + } + + template + typed_value* + typed_value::implicit_value(const T &v) + { + return implicit_value(v, make_textual(v, this, 0)); + } + namespace validators { /* If v.size() > 1, throw validation_error. If v.size() == 1, return v.front() diff --git a/include/boost/program_options/value_semantic.hpp b/include/boost/program_options/value_semantic.hpp index ac9dbc663b..7794ecbfe6 100644 --- a/include/boost/program_options/value_semantic.hpp +++ b/include/boost/program_options/value_semantic.hpp @@ -11,7 +11,6 @@ #include #include -#include #include #include @@ -194,14 +193,10 @@ namespace boost { namespace program_options { /** Specifies default value, which will be used if none is explicitly specified. The type 'T' should - provide operator<< for ostream. + provide operator<< for ostream, or the 'make_textual' + function should be specialized. */ - typed_value* default_value(const T& v) - { - m_default_value = boost::any(v); - m_default_value_as_text = boost::lexical_cast(v); - return this; - } + typed_value* default_value(const T& v); /** Specifies default value, which will be used if none is explicitly specified. Unlike the above overload, @@ -218,15 +213,14 @@ namespace boost { namespace program_options { /** Specifies an implicit value, which will be used if the option is given, but without an adjacent value. - Using this implies that an explicit value is optional, + Using this implies that an explicit value is optional, but if + given, must be strictly adjacent to the option, i.e.: '-ovalue' + or '--option=value'. Giving '-o' or '--option' will cause the + implicit value to be applied. + The type 'T' should provide operator<< for ostream, or the + 'make_textual' function should be specialized. */ - typed_value* implicit_value(const T &v) - { - m_implicit_value = boost::any(v); - m_implicit_value_as_text = - boost::lexical_cast(v); - return this; - } + typed_value* implicit_value(const T &v); /** Specifies the name used to to the value in help message. */ typed_value* value_name(const std::string& name) From 54383c218a14702a002738eae6325f6ad15a8cab Mon Sep 17 00:00:00 2001 From: Ed Catmur Date: Thu, 23 Feb 2023 00:44:31 +0000 Subject: [PATCH 2/2] Update value_semantic.hpp --- .../boost/program_options/detail/value_semantic.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/boost/program_options/detail/value_semantic.hpp b/include/boost/program_options/detail/value_semantic.hpp index 5aef0802ff..cb357ab010 100644 --- a/include/boost/program_options/detail/value_semantic.hpp +++ b/include/boost/program_options/detail/value_semantic.hpp @@ -47,21 +47,21 @@ namespace boost { namespace program_options { } } - template - std::string make_textual(const T& v, typed_value*, long) + template + std::string make_textual(const T& v, value_semantic*, long) { return boost::lexical_cast(v); } - template - std::string make_textual(const std::vector& v, typed_value, charT>*, long) + template + std::string make_textual(const std::vector& v, value_semantic*, long) { std::string textual; for (unsigned i = 0; i < v.size(); ++i) { if (i != 0) textual += ' '; - textual += make_textual(v[i], (typed_value*)0, 0); + textual += make_textual(v[i], (value_semantic*)0, 0); } return textual; }