使用boost::optional增强Boost程序选项

3

我正在尝试编写一个命令行界面(CLI),使用boost::program_options对现有代码进行扩展。该代码库利用了大量的boost::optional参数,因此我希望能够从命令行中解析boost::optional。如果没有指定,结果是boost::none,如果指定,则获得初始化值。

当我尝试使用自定义的Boost验证器时,我遇到了bad_any_cast的问题。以下是出现问题的最小完整可复现示例(MCVE)。

我有一个类

class MyClass {                                                                                                     
public:                                                                                                       
    int x;                                                                                                    
    MyClass(const int a) : x(a) {};                                                                                                                                                                                                                                                                                              
};    

还有一个针对该类的自定义Boost验证器。这种验证器的风格直接来自于Boost文档。

void validate(boost::any& v, const std::vector<std::string>& values,                                          
          MyClass* target_type, int) {                                                                          

    v = boost::any(boost::optional<MyClass>(boost::in_place(1)));                                                   
}

最后,我的主要功能是创建一个简单的解析器。
#include <boost/program_options.hpp>                                                                          
#include <boost/optional.hpp>                                                                                 
#include <boost/optional/optional_io.hpp>                                                                     
#include <boost/utility/in_place_factory.hpp> 

int main(int argc, char** argv) {                                                                             
    po::options_description desc("");                                                                         
    desc.add_options()                                                                                        
        ("MyClass", po::value<boost::optional<MyClass>>()->default_value(boost::none, ""), "MyClass");        

    po::variables_map args;                                                                                   

    po::store(po::parse_command_line(argc, argv, desc), args);                                                

}   

如果我在命令行上没有传递--MyClass选项,代码可以成功运行。然而,如果我传递了--MyClass选项,就会出现bad_any_cast错误。

terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::bad_any_cast> >'
  what():  boost::bad_any_cast: failed conversion using boost::any_cast

我用GDB逐步调试,在MyClassany_cast中抛出了这个异常,但是如果在boost::program_options之外编写类似的代码,它会成功运行。

例如,下面的代码将相同的boost::optional强制转换为boost::any,然后再次强制转换,可以成功运行且没有错误。

#include <iostream>                                                                                           
#include <boost/program_options.hpp>                                                                          
#include <boost/optional.hpp>                                                                                 
#include <boost/optional/optional_io.hpp>                                                                     
#include <boost/utility/in_place_factory.hpp>                                                                 

namespace po = boost::program_options;                                                                        

class MyClass {                                                                                              
public:                                                                                                       
    int x;                                                                                                    
    MyClass(const int a) : x(a) {};                                                                           
};                                                                                                            

int f(boost::any& v) {                                                                                        
    v = boost::any(boost::optional<MyClass>(boost::in_place(1)));                                             
}                                                                                                             


int main(int argc, char** argv) {                                                                             

    boost::any v;                                                                                             
    f(v);                                                                                                     
    boost::any_cast<boost::optional<MyClass>>(v);                                                             
}    

我知道program_options支持default_value,所以我可以在解析后使用if语句将基本值包装在可选项中,但我认为使用上面的自定义验证器方法会更清晰。
是否有人对如何解决此问题有任何想法或建议?

你尝试过将默认值从 boost::none 更改为 boost::optional<MyClass>() 吗?boost::noneboost::optional<MyClass>() 不是相同的类型,因此会导致任何转换失败。 - Alan Birtles
boost::none 实际上与 boost::optional<T> 兼容,并表示可选项不包含值(即 boost::optional<T> v.reset() == boost::none 将计算为 true)。此外,当将 any_cast 应用于 boost::none 时,代码可以正常工作,但是当将其应用于除 boost::none 之外的任何内容时,则无法正常工作。感谢您的建议! - hyperdelia
1个回答

3

验证函数不接受 optional 参数。这是因为类型参数 (target_type) 是 MyClass*,而不是 optional<MyClass>*。(文档)

该函数接受四个参数。第一个参数是值的存储位置,在这种情况下,可以是空的或者包含 magic_number 类的实例。第二个参数是在下一个选项出现时找到的字符串列表。剩下的两个参数是解决某些编译器缺少部分模板特化和部分函数模板排序的问题所需的。

以下是我的理解:

在 Coliru 上实时查看

#include <boost/optional.hpp>
#include <boost/optional/optional_io.hpp>
#include <boost/program_options.hpp>
#include <boost/utility/in_place_factory.hpp>
#include <iostream>
#include <string>

namespace po = boost::program_options;

struct MyClass {
    int x;
    MyClass(int a) : x(a){};

    friend std::ostream& operator<<(std::ostream& os, MyClass const& mc) {
        return os << "MyClass(" << mc.x << ")";
    }
};

void validate(boost::any &v, const std::vector<std::string> &values, MyClass * /*target_type*/, int) {
    v = MyClass(std::stoi(values.front()));
}

int main(int argc, char **argv) {
    po::options_description desc("");
    desc.add_options()("MyClass", po::value<boost::optional<MyClass> >()->default_value(boost::none, ""), "MyClass");

    po::variables_map args;
    store(parse_command_line(argc, argv, desc), args);
    notify(args);

    std::cout << "Arg: " << args["MyClass"].as<boost::optional<MyClass> >() << "\n";
}

针对:

./a.out --MyClass 42
./a.out --MyClass no_chill
./a.out

打印

+ ./a.out --MyClass 42
Arg:  MyClass(42)
+ ./a.out --MyClass no_chill
terminate called after throwing an instance of 'std::invalid_argument'
  what():  stoi
+ ./a.out
Arg: --

额外收益

参考文档的提示,我认为你可以使其更加优雅:

int main(int argc, char **argv) {
    po::options_description desc("");

    boost::optional<MyClass> my_option;
    desc.add_options()("MyClass", po::value(&my_option), "MyClass");

    po::variables_map args;
    store(parse_command_line(argc, argv, desc), args);
    notify(args);

    std::cout << "Arg: " << my_option << "\n";
}

消除所有易错的类型和名称重复。

注意:此方法不适用于低于1.65版本的boost,当编译时用户会看到来自"lexical_cast.hpp"的静态断言错误(请参见1.65版本的发布说明)。

在Coliru上实时运行

输出仍然相同。


我简直不敢相信我错过了如此微不足道的明显问题;感谢您提供详细的答案! - hyperdelia
@RemyPrechelt 我会把这个问题归结为鸭子类型(在这种情况下是 boost::any)。干杯! - sehe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接