[英]Parsing std::chrono::duration with boost::program_options
我正在嘗試使用boost::program_options
( boost
version 1.80.0
和 Apple clang 14.0.0
on arm64-apple-darwin22.2.0
)解析命令行以讀取std::chrono::duration<int>
object 格式為12h34m56s
。 根據文檔,需要在我下面的代碼中重載validate
function,如下所示。
#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;
}
但是,這無法編譯,並且編譯器報告:
/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),
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
我還嘗試過實現istream
operator>>
以及為std::chrono::duration<int>
class 重載boost::lexical_cast
但沒有成功。 我在這里錯過了什么?
我嘗試使std::chrono::duration<int>
std::istream
和std::wistream
都可用,但同樣無濟於事。 請注意, std::chrono::from_stream
在我的編譯器上不可用。
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;
}
您可以重載operator>>
。 請記住,它依賴於 ADL,因此它不需要位於std::chrono
命名空間中。
然而,這很棘手,因為它要么導致其他代碼出現意外,要么甚至冒着跨 TU 違反 ODR 的風險。
相反,請注意validate
還利用 ADL。 最后請注意,您的重載可以在任何關聯的命名空間中: std
(由於vector
和basic_string
), std::chrono
(由於duration) but also
boost (due to
any`)。這大大降低了干擾的可能性當前或未來的標准符號。
所以這里是固定的:
#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";
}
}
打印例如
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp -lboost_program_options
./a.out -d 3m
Demo:
-d [ --duration ] arg test
Value: 180s
您得到的錯誤似乎是您沒有為std::wistream
定義的operator>>
(即錯誤消息中的std::basic_istream<wchar_t>
)。
此外,您可以使用from_stream
從 stream 中提取std::chrono::duration
。 這樣就可以很容易地代替您手動解析代碼。
我認為更好的方法是從boost conversion library添加lexical_cast
或try_lexical_convert
方法的專門化。
程序選項中的代碼在內部調用boost::lexical_cast<>
(來自轉換庫)以在文本和所需類型之間進行轉換,並且轉換在內部使用try_lexical_convert
。
這樣您就不必在每次需要支持另一種類型時都重復樣板驗證代碼(即,諸如check_first_occurrence
和get_single_string
類的代碼)。
另外,如果你想設置默認值和 output options_description
,那么你還必須添加從持續時間到字符串的轉換。
看看我用來在配置文件中設置超時的示例代碼(因此它只支持數字后跟 us/ms/s 單位):
#include <boost/program_options.hpp>
#include <chrono>
#include <string>
#include <cstdlib>
#include <regex>
namespace boost::conversion::detail {
template<>
bool try_lexical_convert<std::chrono::microseconds, std::string>(const std::string& in, std::chrono::microseconds& out){
std::smatch m;
if(!std::regex_match(in, m, std::regex(R"((\d+(?:\.\d*)?)\s*([um]?)s)",std::regex_constants::icase)))
return false;
std::string number = m.str(1);
std::string unit = m.str(2);
char * end;
double count = std::strtod(number.data(), &end);
if(number.data() + number.length() != end)
return false;
if (unit.empty()) count *= 1e6;
else if(unit == "m" ) count *= 1e3;
else if(unit != "u" ) assert(false);
out = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::duration<double, std::micro>(count));
return true;
}
template<>
bool try_lexical_convert<std::string, std::chrono::microseconds>(const std::chrono::microseconds& in, std::string& out){
out = std::to_string(in.count()) + "us";
return true;
}
}
#include <iostream>
int main(int argc, char *argv[]) {
using namespace std::literals;
using namespace boost::program_options;
std::chrono::microseconds us;
options_description opts;
opts.add_options()("time,t", value(&us)->default_value(10ms), "some time");
std::cout << opts;
variables_map vm;
store(parse_command_line(argc, argv, opts), vm);
notify(vm);
std::cout << us.count() << "us\n" << us.count()/1e3 << "ms\n" << us.count()/1e6 << "s" << std::endl;
return 0;
}
(這應該與 C++17 一起編譯,並且稍作改動它也應該與 C++11 一起工作)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.