簡體   English   中英

使用 boost::program_options 解析 std::chrono::duration

[英]Parsing std::chrono::duration with boost::program_options

我正在嘗試使用boost::program_optionsboost 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但沒有成功。 我在這里錯過了什么?

在@rubenvb 的回答后編輯

我嘗試使std::chrono::duration<int> std::istreamstd::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 (由於vectorbasic_string ), std::chrono (由於duration) but also boost (due to any`)。這大大降低了干擾的可能性當前或未來的標准符號。

所以這里是固定的:

生活在 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";
    }
}

打印例如

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_casttry_lexical_convert方法的專門化。

程序選項中的代碼在內部調用boost::lexical_cast<> (來自轉換庫)以在文本和所需類型之間進行轉換,並且轉換在內部使用try_lexical_convert

這樣您就不必在每次需要支持另一種類型時都重復樣板驗證代碼(即,諸如check_first_occurrenceget_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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM