【问题标题】:boost program_options values with different types使用不同类型提升 program_options 值
【发布时间】:2021-03-16 17:04:51
【问题描述】:

如何为 boost program_options 提供一个选项,该选项将接受 3 个不同类型的值:[int, int, float]?

例如:

./app_name --check-integrity 1 2.2 --do_something 2 2 2.5

我尝试使用 boost::any 向量来实现这一点。

namespace po = boost::program_options;
po::option_descriptions desc("");

desc.add_options()
 ("opt", po::value<std::vector<boost::any> >()->multitoken(), "description");

但没有成功。此代码导致:

/usr/include/boost/lexical_cast/detail/converter_lexical.hpp:243:13: error: static assertion failed: 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),

还有什么想法吗?

【问题讨论】:

  • 请澄清"which will accept 3 values with different types: [int, int, float]" 的意思。您能否提供一些在命令行中输入/接受的示例?

标签: c++ boost boost-program-options


【解决方案1】:

这是我推荐的更简单的方法。为了完整起见,我添加了一个真正的 multi-token option value parsing approach 作为单独的答案。

您可以使用任何可流式传输的类型。所以,如果你有类型:

struct check {
    int i;
    double d;
};

struct something {
    int a;
    int b;
    double c;
};

您可以定义如下选项:

opt.add_options()
    ("do_something,s", bpo::value<something>()->default_value(something{1,2,3}, "1 2 3"), "")
    ("check-integrity,c", bpo::value<check>()->default_value(check{1,0.1}, "1 0.1"), "")
    ("help", "");

请注意,仅当类型无法输出流式传输(或流式传输格式与预期的输入格式不匹配)时,您才需要提供字符串化的默认值

bpo::variables_map vm;

try {
    bpo::store(parse_command_line(argc, argv, opt), vm);

    bpo::notify(vm);

    if (vm.count("help")) {
        std::cout << opt << std::endl;
        return 0;
    }

    std::cout << vm["do_something"].as<something>() << "\n";
    std::cout << vm["check-integrity"].as<check>() << "\n";
} catch (std::exception const& e) {
    std::cerr << e.what() << "\n";
}

输出流:简单的东西

这是通常的东西:

static inline std::ostream& operator<<(std::ostream& os, check const& v) {
    return os << "check {" << v.i << ", " << v.d << "}";
}
static inline std::ostream& operator<<(std::ostream& os, something const& v) {
    return os << "something {" << v.a << ", " << v.b << ", " << v.c << "}";
}

输入流:随心所欲

这里有一些轻微的警告。首先是program_options 提供的istream&amp; - 默认情况下 - 使用std::noskipws 操纵器进行修改。您必须撤消该操作才能读取空格分隔的数据。

接下来,对于任何重要的解析/验证任务,我建议使用更复杂的东西。但是对于演示,这就足够了:

static inline std::istream& operator>>(std::istream& is, check& into) {
    return is >> std::skipws >> into.i >> into.d;
}
static inline std::istream& operator>>(std::istream& is, something& into) {
    return is >> std::skipws >> into.a >> into.b >> into.c;
}

演示时间

Live On Coliru

#include <boost/program_options.hpp>
#include <iostream>
#include <iomanip>

namespace bpo = boost::program_options;
struct check {
    int i;
    double d;
};

struct something {
    int a;
    int b;
    double c;
};

static inline std::ostream& operator<<(std::ostream& os, check const& v) {
    return os << "check {" << v.i << ", " << v.d << "}";
}
static inline std::ostream& operator<<(std::ostream& os, something const& v) {
    return os << "something {" << v.a << ", " << v.b << ", " << v.c << "}";
}
static inline std::istream& operator>>(std::istream& is, check& into) {
    return is >> std::skipws >> into.i >> into.d;
}
static inline std::istream& operator>>(std::istream& is, something& into) {
    return is >> std::skipws >> into.a >> into.b >> into.c;
}

int main(int argc, char* argv[]) {
    bpo::options_description opt("all options");

    opt.add_options()
        ("do_something,s", bpo::value<something>()->default_value(something{1,2,3}, "1 2 3"), "")
        ("check-integrity,c", bpo::value<check>()->default_value(check{1,0.1}, "1 0.1"), "")
        ("help", "");

    bpo::variables_map vm;

    try {
        bpo::store(parse_command_line(argc, argv, opt), vm);

        bpo::notify(vm);

        if (vm.count("help")) {
            std::cout << opt << std::endl;
            return 0;
        }

        std::cout << vm["do_something"].as<something>() << "\n";
        std::cout << vm["check-integrity"].as<check>() << "\n";
    } catch (std::exception const& e) {
        std::cerr << e.what() << "\n";
    }
}

打印

+ ./sotest --help
all options:
  -s [ --do_something ] arg (=1 2 3)
  -c [ --check-integrity ] arg (=1 0.1)
  --help 

+ ./sotest
something {1, 2, 3}
check {1, 0.1}
+ ./sotest --check ''
the argument for option '--check-integrity' is invalid
+ ./sotest --check oops
the argument ('oops') for option '--check-integrity' is invalid
+ ./sotest --check '8 8'
something {1, 2, 3}
check {8, 8}
+ ./sotest --do_something '11 22 .33'
something {11, 22, 0.33}
check {1, 0.1}
+ ./sotest --do_something '11 22 .33' --check '80 -8'
something {11, 22, 0.33}
check {80, -8}

【讨论】:

【解决方案2】:

我之前发过another answer,但我只能预测后续问题:

但是如果我想允许 --check 1 3.14 而不是 --check "1 3.14"--check '1 3.14' 怎么办?

这不是一个坏问题,而且实际上是公平的,因为这是对原始帖子的仔细阅读。

我认为这不是最佳策略,因为:

  • 它需要解析成一些中间标记向量
  • 需要从这些类型手动转换为目标类型
  • 它使命令行变得脆弱
    • 它将选项值的验证推迟到命令行解析之后,多标记可能有一个意想不到的 number 个值标记,并且在转换为目标类型之前你不会知道
    • 它根本不起作用,例如其中一个令牌是否定的。因为--check 8 -8 会尝试解析-8 作为选项

一般来说,功能越少,工作量就越大。

你会怎么做呢?

让我们假设与之前相同的目标类型:

struct check {
    int i;
    double d;
    friend std::ostream& operator<<(std::ostream& os, check const& v) {
        return os << "check {" << v.i << ", " << v.d << "}";
    }
};

struct something {
    int a;
    int b;
    double c;
    friend std::ostream& operator<<(std::ostream& os, something const& v) {
        return os << "something {" << v.a << ", " << v.b << ", " << v.c << "}";
    }
};

解析多令牌

现在,您建议boost::any,但我们可以更具体。我们知道我们只期望整数或双精度数:

using ArgVal = boost::variant<int, double>;
using ArgVec = std::vector<ArgVal>;

通过更具体,您可以更早地发现更多错误(参见例如 提供 'oops' 作为值时解析错误)

现在将选项定义为多令牌:

opt.add_options()
    ("do_something,s", bpo::value<ArgVec>()
        ->multitoken()
        ->default_value({1,2,3}, "1 2 3"), "")
    ("check-integrity,c", bpo::value<ArgVec>()
         ->multitoken()
         ->default_value({1,0.1}, "1 0.1"), "")
    ("help", "");

到目前为止,我认为这实际上非常优雅。

按照我们希望的方式编写演示程序的其余部分:

bpo::variables_map vm;

try {
    bpo::store(parse_command_line(argc, argv, opt), vm);

    bpo::notify(vm);

    if (vm.count("help")) {
        std::cout << opt << std::endl;
        return 0;
    }

    std::cout << as_check(vm["check-integrity"].as<ArgVec>()) << "\n";
    std::cout << as_something(vm["do_something"].as<ArgVec>()) << "\n";
} catch (std::exception const& e) {
    std::cerr << "ERROR " << e.what() << "\n";
}

这留下了一些松散的结局:

  • 正在解析
  • 转化(什么是as_checkas_something?)

解析ArgVal

和之前一样,我们可以简单地为该类型提供一个输入流操作符。正如我们所说的back then,您可以“随心所欲”。

让我们使用 Spirit X3 来获得更多的里程:

static inline std::istream& operator>>(std::istream& is, ArgVal& into) {
    namespace x3 = boost::spirit::x3;
    std::string arg;
    getline(is, arg);

    x3::real_parser<double, x3::strict_real_policies<double> > real_;
    if (!phrase_parse(
            begin(arg), end(arg),
            (real_ | x3::int_) >> x3::eoi,
            x3::blank,
            into))
    {
        is.setstate(std::ios::failbit);
    }
    return is;
}

这次我们拥抱noskipws,因为它召集了我们,将完整的参数放入字符串中。

然后,我们将其解析为严格的双精度或整数。

通过使用严格的真实策略,我们避免将任何整数解析为双精度数,因为以后我们不希望允许从双精度数转换为整数(可能会丢失信息)。

转换

我们需要访问器来提取整数或双精度值。我们将允许从 int 转换为 double,因为它永远不会丢失精度(--check 8 8--check 8 8.0 一样有效)。

static int as_int(ArgVal const& a) {
    return boost::get<int>(a);
}

static double as_double(ArgVal const& a) {
    if (auto pi = boost::get<int>(&a))
        return *pi; // non-lossy conversion allowed
    return boost::get<double>(a);
}

现在我们可以用 as_int as_double 助手来表达对目标类型的更高级别的转换:

static check as_check(ArgVec const& av) {
    try { 
        if (av.size() != 2) throw "up"; 
        return { as_int(av.at(0)), as_double(av.at(1)) };
    } catch(...) {
        throw std::invalid_argument("expected check (int, double)");
    }
}

static something as_something(ArgVec const& av) {
    try { 
        if (av.size() != 3) throw "up"; 
        return { as_int(av.at(0)), as_int(av.at(1)), as_double(av.at(2)) };
    } catch(...) {
        throw std::invalid_argument("expected something (int, int, double)");
    }
}

我尝试防御性地写作,但不是特别好的风格。不过,代码是安全的。

演示时间

Live On Coliru

#include <boost/program_options.hpp>
#include <iostream>
#include <iomanip>

namespace bpo = boost::program_options;

    struct check {
        int i;
        double d;
        friend std::ostream& operator<<(std::ostream& os, check const& v) {
            return os << "check {" << v.i << ", " << v.d << "}";
        }
    };

    struct something {
        int a;
        int b;
        double c;
        friend std::ostream& operator<<(std::ostream& os, something const& v) {
            return os << "something {" << v.a << ", " << v.b << ", " << v.c << "}";
        }
    };

#include <boost/spirit/home/x3.hpp>
using ArgVal = boost::variant<int, double>;
using ArgVec = std::vector<ArgVal>;

static inline std::istream& operator>>(std::istream& is, ArgVal& into) {
    namespace x3 = boost::spirit::x3;
    std::string arg;
    getline(is, arg);

    x3::real_parser<double, x3::strict_real_policies<double> > real_;
    if (!phrase_parse(
            begin(arg), end(arg),
            (real_ | x3::int_) >> x3::eoi,
            x3::blank,
            into))
    {
        is.setstate(std::ios::failbit);
    }
    return is;
}

static int as_int(ArgVal const& a) {
    return boost::get<int>(a);
}

static double as_double(ArgVal const& a) {
    if (auto pi = boost::get<int>(&a))
        return *pi; // non-lossy conversion allowed
    return boost::get<double>(a);
}

static check as_check(ArgVec const& av) {
    try { 
        if (av.size() != 2) throw "up"; 
        return { as_int(av.at(0)), as_double(av.at(1)) };
    } catch(...) {
        throw std::invalid_argument("expected check (int, double)");
    }
}

static something as_something(ArgVec const& av) {
    try { 
        if (av.size() != 3) throw "up"; 
        return { as_int(av.at(0)), as_int(av.at(1)), as_double(av.at(2)) };
    } catch(...) {
        throw std::invalid_argument("expected something (int, int, double)");
    }
}

int main(int argc, char* argv[]) {
    bpo::options_description opt("all options");

    opt.add_options()
        ("do_something,s", bpo::value<ArgVec>()
            ->multitoken()
            ->default_value({1,2,3}, "1 2 3"), "")
        ("check-integrity,c", bpo::value<ArgVec>()
             ->multitoken()
             ->default_value({1,0.1}, "1 0.1"), "")
        ("help", "");

    bpo::variables_map vm;

    try {
        bpo::store(parse_command_line(argc, argv, opt), vm);

        bpo::notify(vm);

        if (vm.count("help")) {
            std::cout << opt << std::endl;
            return 0;
        }

        std::cout << as_check(vm["check-integrity"].as<ArgVec>()) << "\n";
        std::cout << as_something(vm["do_something"].as<ArgVec>()) << "\n";
    } catch (std::exception const& e) {
        std::cerr << "ERROR " << e.what() << "\n";
    }
}

打印

+ ./sotest --help
all options:
  -s [ --do_something ] arg (=1 2 3)
  -c [ --check-integrity ] arg (=1 0.1)
  --help 

+ ./sotest
check {1, 0.1}
something {1, 2, 3}
+ ./sotest --check ''
ERROR the argument for option '--check-integrity' is invalid
+ ./sotest --check oops
ERROR the argument ('oops') for option '--check-integrity' is invalid
+ ./sotest --check 8 8
check {8, 8}
something {1, 2, 3}
+ ./sotest --do_something 11 22 .33
check {1, 0.1}
something {11, 22, 0.33}
+ ./sotest --do_something 11 22 .33 --check 80 8
check {80, 8}
something {11, 22, 0.33}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-05
    • 2011-01-01
    • 2012-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多