我之前发过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_check和as_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}