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?
Edit after @rubenvb 's answer
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;
}
CodePudding user response:
You can overload operator>>
. Keep in mind it relies on ADL, though, so it needs ot 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 to
any`)!This vastly reduces the potential to interfere with current or future standard symbols.
So here's fixed:
#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
CodePudding user response:
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.