I'm trying to parse the command line using boost::program_options (boost version 1.80.0, and Apple clang 14.0.0 on arm64-apple-darwin22.2.0) in order to read a std::chrono::duration<int> object formatted as 12h34m56s. According to the documentation, one is required to overload the validate function as follows in my code below.
#include <boost/program_options.hpp>
#include <iostream>
namespace po = boost::program_options;
using Duration = std::chrono::duration<int>;
inline void validate(boost::any &value, const std::vector<std::string> &values, Duration* target_type) {
using namespace std::chrono_literals;
try {
po::validators::check_first_occurrence(value);
auto duration_str = po::validators::get_single_string(values);
Duration duration = Duration::zero();
std::string::size_type i = 0;
while (i < duration_str.size()) {
std::string::size_type j = i;
while (j < duration_str.size() && std::isdigit(duration_str[j])) {
++j;
}
int v = std::stoi(duration_str.substr(i, j - i));
i = j;
if (i < duration_str.size() && duration_str[i] == 'h') {
duration += v * 1h;
++i;
} else if (i < duration_str.size() && duration_str[i] == 'm') {
duration += v * 1min;
++i;
} else if (i < duration_str.size() && duration_str[i] == 's') {
duration += v * 1s;
++i;
}
}
value = boost::any(duration);
} catch (...) {
throw po::invalid_option_value("Invalid duration");
}
}
int main(int ac, char *av[])
{
try
{
po::options_description desc("Allowed options");
desc.add_options()
("help,h", "produce a help screen")
("duration,d", po::value<Duration>(), "duration in 12h34m56s format")
;
po::variables_map vm;
po::store(po::parse_command_line(ac, av, desc), vm);
if (vm.count("help"))
{
std::cout << desc;
return 0;
}
if (vm.count("duration"))
{
std::cout << "The duration is \""
<< vm["duration"].as<Duration>().count()
<< "\"\n";
}
}
catch (std::exception& e)
{
std::cout << e.what() << "\n";
}
return 0;
}
However, this fails to compile, and the compiler reports that:
/opt/homebrew/include/boost/lexical_cast/detail/converter_lexical.hpp:243:13: error: static_assert failed due to requirement 'has_right_shift<std::istream, std::chrono::duration<int, std::ratio<1, 1>>, boost::binary_op_detail::dont_care>::value || boost::has_right_shift<std::wistream, std::chrono::duration<int, std::ratio<1, 1>>, boost::binary_op_detail::dont_care>::value' "Target type is neither std::istream`able nor std::wistream`able"
BOOST_STATIC_ASSERT_MSG((result_t::value || boost::has_right_shift<std::basic_istream<wchar_t>, T >::value),
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I've also tried implementing the istream operator>> as well as overloading boost::lexical_cast for the std::chrono::duration<int> class but with no success. What am I missing here?
I tried making std::chrono::duration<int> both std::istream and std::wistream able, but again to no avail. Note that std::chrono::from_stream is not available on my compiler.
template <typename String>
inline Duration parseDuration(const String& duration_str)
{
using namespace std::chrono_literals;
Duration duration;
typename String::size_type i = 0;
while (i < duration_str.size()) {
std::wstring::size_type j = i;
while (j < duration_str.size() && std::iswdigit(duration_str[j])) {
++j;
}
int v = std::stoi(duration_str.substr(i, j - i));
i = j;
if (i < duration_str.size() && duration_str[i] == 'h') {
duration += v * 1h;
++i;
} else if (i < duration_str.size() && duration_str[i] == 'm') {
duration += v * 1min;
++i;
} else if (i < duration_str.size() && duration_str[i] == 's') {
duration += v * 1s;
++i;
}
}
return duration;
}
inline std::wistream& operator>>(std::wistream& is, Duration& duration) {
std::wstring duration_str;
is >> duration_str;
duration = parseDuration(duration_str);
return is;
}
inline std::istream& operator>>(std::istream& is, Duration& duration) {
std::string duration_str;
is >> duration_str;
duration = parseDuration(duration_str);
return is;
}
You can overload operator>>. Keep in mind it relies on ADL, though, so it needs to be in std::chrono namespace.
However, that's icky, as it will either lead to surprises to other code or even risk ODR violations across TUs.
Instead, notice that validate also leverages ADL. Finally note that your overload can be in any of the associated namespaces: std (due to vector and basic_string), std::chrono (due to duration) but also boost(due toany`)!This vastly reduces the potential to interfere with current or future standard symbols.
So here's fixed:
Live On Coliru
#include <boost/program_options.hpp>
#include <chrono>
#include <iostream>
namespace po = boost::program_options;
using Duration = std::chrono::duration<int>;
namespace boost {
template <class CharT>
void validate(boost::any& value, std::vector<std::basic_string<CharT>> const& values, Duration*, int) {
using namespace std::chrono_literals;
try {
po::validators::check_first_occurrence(value);
auto duration_str = po::validators::get_single_string(values);
Duration duration = Duration::zero();
std::string::size_type i = 0;
while (i < duration_str.size()) {
std::string::size_type j = i;
while (j < duration_str.size() && std::isdigit(duration_str[j])) {
++j;
}
int v = std::stoi(duration_str.substr(i, j - i));
i = j;
if (i < duration_str.size() && duration_str[i] == 'h') {
duration += v * 1h;
++i;
} else if (i < duration_str.size() && duration_str[i] == 'm') {
duration += v * 1min;
++i;
} else if (i < duration_str.size() && duration_str[i] == 's') {
duration += v * 1s;
++i;
}
}
value = boost::any(duration);
} catch (...) {
throw po::invalid_option_value("Invalid duration");
}
}
} // namespace boost
int main(int argc, char** argv) {
po::options_description opts("Demo");
opts.add_options() //
("duration,d", po::value<Duration>(), "test") //
;
std::cout << opts << "\n";
po::variables_map vm;
store(po::parse_command_line(argc, argv, opts), vm);
if (vm.contains("duration")) {
std::cout << "Value: " << vm["duration"].as<Duration>() << "\n";
}
}
Prints e.g.
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp -lboost_program_options
./a.out -d 3m
Demo:
-d [ --duration ] arg test
Value: 180s
It seems the error you're getting is that you don't have an operator>> defined for std::wistream (i.e. the std::basic_istream<wchar_t> in your error message).
Additionally, you can extract a std::chrono::duration from a stream using from_stream. So that can easily replace your manual parsing code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With