问题描述
Boost 的 program_options
库现在 supports boost::optional,可以用 std::optional
做同样的事情吗?
我试图同时修改 documentation example 和 PR 中的代码,但似乎都不起作用。
例如,非常简单的整数情况(在尝试模板特化之前):
void validate(boost::any& v,const std::vector<std::string>& values,std::optional<int>* target_type,int) {
using namespace boost::program_options;
validators::check_first_occurrence(v);
const string& s = validators::get_single_string(values);
int n = lexical_cast<int>(s);
v = any(std::make_optional<int>(n));
}
失败,错误是目标类型不istream
:
external/boost/boost/lexical_cast/detail/converter_lexical.hpp:243:13:
error: static_assert Failed due to requirement
'has_right_shift<std::__1::basic_istream<char>,std::__1::optional<int>,boost::binary_op_detail::dont_care>::value || boost::has_right_shift<std::__1::basic_istream<wchar_t>,boost::binary_op_detail::dont_care>::value'
"Target type is neither std::istream`able nor std::wistream`able"
解决方法
诸如validate
(以及operator>>
)之类的问题通常是ADL¹。
您需要在关联命名空间之一中声明重载。在这种情况下,因为 int
是原始类型,唯一关联的命名空间来自库代码:
-
std
代表optional
、vector
、string
、allocator
、char_traits
(是的,这些都算!) -
boost
为any
您不希望在这些命名空间中添加您的代码,因为您可能会干扰库函数或在库实现细节发生变化时招致未来的破坏。
如果必须选择,您更愿意在这里选择boost
,因为
- 这是提供手头功能的库
-
validate
免费功能明确设计为自定义点
旁注:留意 tag_invoke
- 在库中构建自定义点的更好方法
修复
说了这么多,解决办法很简单:
namespace boost::{
void validate(boost::any& v,const std::vector<std::string>& values,std::optional<int>*,int) {
using namespace boost::program_options;
validators::check_first_occurrence(v);
const std::string& s = validators::get_single_string(values);
int n = boost::lexical_cast<int>(s);
v = boost::any(std::make_optional<int>(n));
}
} // namespace boost
添加两行使其工作:Live On Wandbox。
其他注意事项:
-
注入
operator>>
的“解决方案”一般不太纯 因为 -
在最近的编译器中,您可以说
vm.contains
而不是略带辱骂性的vm.count
-
非流式类型还有另一个障碍,如果您定义默认值,您可能还需要使用它指定字符串表示。
列表
Compiling on Compiler Explorer
#include <boost/program_options.hpp>
#include <optional>
#include <iostream>
namespace po = boost::program_options;
namespace boost {
void validate(boost::any& v,int) {
using namespace boost::program_options;
validators::check_first_occurrence(v);
const std::string& s = validators::get_single_string(values);
int n = boost::lexical_cast<int>(s);
v = boost::any(std::make_optional<int>(n));
}
} // namespace boost
int main(int ac,char* av[]) {
try {
using Value = std::optional<int>;
po::options_description desc("Allowed options");
desc.add_options()
("help","produce help message")
("value",po::value<Value>()->default_value(10,"10"),"value")
;
po::variables_map vm;
po::store(po::parse_command_line(ac,av,desc),vm);
po::notify(vm);
if (vm.contains("value")) {
std::cout << "value is " << vm["value"].as<Value>().value() << "\n";
}
} catch (std::exception& e) {
std::cout << e.what() << "\n";
return 1;
}
}
奖金
作为附加练习,让我们演示一下,如果您的可选 value_type 不是原始类型,而是在命名空间 MyLib
中声明的库类型,那么我们没有大多数上述权衡:
namespace MyLib {
template <typename T> struct MyValue {
MyValue(T v = {}) : value(std::move(v)) {}
private:
T value;
friend std::istream& operator>>(std::istream& is,MyValue& mv) {
return is >> mv.value;
}
friend std::ostream& operator<<(std::ostream& os,MyValue const& mv) {
return os << mv.value;
}
};
现在您可以为 MyLib 命名空间中的任何类型提供通用验证器,无论是否可选,并让 ADL 通过您的 MyLib
命名空间找到它们:
template <typename T,typename Values>
void validate(boost::any& v,Values const& values,T*,int) {
po::validators::check_first_occurrence(v);
v = boost::lexical_cast<T>(
po::validators::get_single_string(values));
}
template <typename T,std::optional<T>*,int) {
po::validators::check_first_occurrence(v);
v = std::make_optional(
boost::lexical_cast<T>(
po::validators::get_single_string(values)));
}
} // namespace MyLib
#include <boost/program_options.hpp>
#include <iostream>
#include <iomanip>
namespace po = boost::program_options;
namespace MyLib {
template <typename T> struct MyValue {
MyValue(T v = {}) : value(std::move(v)) {}
private:
T value;
friend std::istream& operator>>(std::istream& is,MyValue& mv) {
return is >> std::boolalpha >> mv.value;
}
friend std::ostream& operator<<(std::ostream& os,MyValue const& mv) {
return os << std::boolalpha << mv.value;
}
};
// Provide generic validators for any types in your MyLib namespace,be it
// optional or not
template <typename T,int) {
po::validators::check_first_occurrence(v);
v = std::make_optional(
boost::lexical_cast<T>(
po::validators::get_single_string(values)));
}
} // namespace MyLib
int main(int ac,char* av[]) {
try {
using Int = MyLib::MyValue<int>;
using OptInt = std::optional<MyLib::MyValue<int>>;
using OptStr = std::optional<MyLib::MyValue<std::string> >;
po::options_description desc("Allowed options");
desc.add_options()
("ival",po::value<Int>()->default_value(Int{10}),"integer value")
("opti",po::value<OptInt>()->default_value(OptInt{},"(nullopt)"),"optional integer value")
("sval",po::value<OptStr>()->default_value(OptStr{"secret"},"'secret'"),"optional string value")
;
po::variables_map vm;
po::store(po::parse_command_line(ac,vm);
po::notify(vm);
std::cout << "Options: " << desc << "\n";
if (vm.contains("ival")) {
std::cout << "ival is " << vm["ival"].as<Int>() << "\n";
}
if (vm.contains("opti")) {
if (auto& v = vm["opti"].as<OptInt>())
std::cout << "opti is " << v.value() << "\n";
else
std::cout << "opti is nullopt\n";
}
if (vm.contains("sval")) {
if (auto& v = vm["sval"].as<OptStr>())
std::cout << "sval is " << v.value() << "\n";
else
std::cout << "sval is nullopt\n";
}
} catch (std::exception& e) {
std::cout << e.what() << "\n";
return 1;
}
}
对于 ./a.out --ival=42 --sval=LtUaE
打印:
Options: Allowed options:
--ival arg (=10) integer value
--opti arg (=(nullopt)) optional integer value
--sval arg (='secret') optional string value
ival is 42
opti is nullopt
sval is LtUaE
¹ 另见另见 Why Does Boost Use a Global Function Override to Implement Custom Validators in "Program Options"