Пользовательская функция проверки для анализа std::chrono::milliseconds с помощью параметров программы Boost

Я пытаюсь проанализировать параметр через параметры программы Boost, которые содержат время в [с] или [мс]. В настоящее время переменная жестко запрограммирована с использованием литералов:

std::chrono::milliseconds timeout = 10s;

Я рад определить это как это в файле конфигурации

#time in [s]
timeout = 10

Однако я не мог понять, как выполнить функцию проверки. Вот что пробовал:

struct chrono_ms : public std::chrono::milliseconds {};

void validate(  boost::any& v,
                const std::vector<std::string>& values,
                chrono_ms*,
                int)
{
    // Make sure no previous assignment to 'v' was made.
    validators::check_first_occurrence(v);

    // Extract the first string from 'values'. If there is more than
    // one string, it's an error, and exception will be thrown.
    const std::string& s = validators::get_single_string(values);

    // Convert to std::chrono::milliseconds.
    v = std::chrono::milliseconds(std::stoi(s));
}   // validate() for std::chrono::milliseconds

Как ни странно, мне удалось написать функцию проверки для std::array, но я не знаком с std::chrono и не мог понять, как это сделать... любые предложения будут высоко оценены, Благодарю.


person chocobo_ff    schedule 13.07.2017    source источник
comment
Можете ли вы показать нам проблему? Этот код выглядит нормально, он компилируется. Включите SSCCE, демонстрирующий проблему.   -  person sehe    schedule 13.07.2017


Ответы (1)


Помимо вопроса (который не ясен), весь смысл std::chrono в том, что вы НЕ ДОЛЖНЫ знать об устройстве.

Просто сделайте аргумент std::chrono::duration<>.

Проблемы дизайна

Во-первых, я подозреваю, что настоящая проблема у вас была в том, что ваш chrono_ms не поддается конструированию. Вам нужно наследовать некоторые конструкторы, например, например.

using clock = std::chrono::steady_clock;

struct duration : public clock::duration {
    using clock::duration::duration;
};

Далее, есть проблемы, связанные со стандартными типами библиотек, которые не были созданы для того, чтобы быть производными от них. Например, тип 5 * chrono_ms(1) будет не chrono_ms, а std::chrono::milliseconds.

Также проблемы с неявными преобразованиями (из-за наследования явных конструкторов).

По этой причине я бы предложил вместо этого простую оболочку:

using clock = std::chrono::steady_clock;
struct duration {
    clock::duration value;
};

Это заставляет вас явно писать то, что вы имеете в виду, и не делать сюрпризов.

Подтвердить

Далее, вот мое предложение для синтаксического анализатора параметров, который принимает единицу измерения:

template<class charT>
void validate(boost::any& v, const std::vector< std::basic_string<charT> >& xs, duration*, long)
{
    po::validators::check_first_occurrence(v);
    std::basic_string<charT> s(po::validators::get_single_string(xs));

    int magnitude;
    clock::duration factor;

    namespace qi = boost::spirit::qi;
    qi::symbols<char, clock::duration> unit;
    unit.add("s",1s)("ms",1ms)("us",1us)("µs",1us)("m",1min)("h",1h);

    if (parse(s.begin(), s.end(), qi::int_ >> (unit|qi::attr(1s)) >> qi::eoi, magnitude, factor))
        v = duration {magnitude * factor};
    else
        throw po::invalid_option_value(s);
}

Вам не нужно помещать это в пространство имен boost или program_options. ADL найдет его (вероятно, именно по этой причине вам нужен "сильный" typedef, такой как ваш chrono_ms).

Тестовая программа:

Жить на Wandbox

#include <boost/program_options.hpp>
#include <boost/spirit/include/qi.hpp>
#include <chrono>
#include <vector>
#include <iostream>

namespace po = boost::program_options;
using namespace std::chrono_literals;

namespace myns {
    using clock = std::chrono::steady_clock;
    struct duration {
        clock::duration value;

        friend std::ostream& operator<<(std::ostream& os, duration const& holder) {
            using namespace std::chrono;
            auto ms = duration_cast<milliseconds>(holder.value).count();

            if (ms >= 1000)
                return os << (ms/1000) << "s";
            else
                return os << ms << "ms";
        }
    };

    template<class charT>
    void validate(boost::any& v, const std::vector< std::basic_string<charT> >& xs, duration*, long)
    {
        po::validators::check_first_occurrence(v);
        std::basic_string<charT> s(po::validators::get_single_string(xs));

        int magnitude;
        clock::duration factor;

        namespace qi = boost::spirit::qi;
        qi::symbols<char, clock::duration> unit;
        unit.add("s",1s)("ms",1ms)("us",1us)("µs",1us)("m",1min)("h",1h);

        if (parse(s.begin(), s.end(), qi::int_ >> unit >> qi::eoi, magnitude, factor))
            v = duration {magnitude * factor};
        else
            throw po::invalid_option_value(s);
    }

}

int main() {
    po::options_description options;
    options.add_options()
        ("duration,d", po::value<myns::duration>(), "duration (e.g. 1s or 10ms)");

    char const* tests[][3] = {
        { "", "-d", "1s" },
        { "", "-d", "2200us" },
        { "", "-d", "10ms" },
        { "", "-d", "5m" },
        { "", "-d", "24h" },
        // 
        { "", "-d", "s" }, // invalid
        { "", "-d", "5" }, // invalid
    };

    for (auto args : tests) try {
        std::copy(args, args +3, std::ostream_iterator<std::string>(std::cout << "Test ", " "));
        auto parsed = po::parse_command_line(3, args, options);
        po::variables_map vm;
        po::store(parsed, vm);
        po::notify(vm);

        std::cout << "\tduration=" << vm["duration"].as<myns::duration>() << "\n";
    } catch (std::exception const& e) {
        std::cout << "\t" << e.what() << "\n";
    }
}

Отпечатки

Test  -d 1s     duration=1s
Test  -d 2200us     duration=2ms
Test  -d 10ms   duration=10ms
Test  -d 5m     duration=300s
Test  -d 24h    duration=86400s
Test  -d s  Error 'the argument ('s') for option '--duration' is invalid'
Test  -d 5  Error 'the argument ('5') for option '--duration' is invalid'

БОНУС

Если вы, например. хотите сделать определенную единицу измерения по умолчанию, замените unit в выражении парсера, например, на (unit|qi::attr(1s)):

Test  -d 1s     duration=1s
Test  -d 2200us     duration=2ms
Test  -d 10ms   duration=10ms
Test  -d 5m     duration=300s
Test  -d 24h    duration=86400s
Test  -d s  the argument ('s') for option '--duration' is invalid
Test  -d 5  duration=5s
person sehe    schedule 13.07.2017
comment
Спасибо, сехе, это сработало как шарм. Многому научился из вашего кода, но std::chrono все еще совершенно чужд мне, поэтому я буду читать об этом сегодня. Спасибо! - person chocobo_ff; 14.07.2017