【问题标题】:Boost program_options with boost::optional使用 boost::optional 提升 program_options
【发布时间】:2020-05-13 14:21:31
【问题描述】:

我正在尝试使用 boost::program_options 将 CLI 编写到使用大量 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)));                                                   
}

最后,我的 main 函数创建了一个简单的解析器。

#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,这被扔进了 MyClass any_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&lt;MyClass&gt;()boost::noneboost::optional&lt;MyClass&gt;() 的类型不同,因此会导致 any 强制转换失败
  • boost::none 实际上与boost::optional&lt;T&gt; 兼容,并表示可选不包含值(即boost::optional&lt;T&gt; v.reset() == boost::none 将评估为真)。此外,当any_cast 应用于boost::none 时,该代码有效,而当它应用于boost::none 以外的任何内容时,代码就无效。不过感谢您的建议!

标签: c++ boost


【解决方案1】:

验证函数不带可选项。类型参数 (target_type) 是 MyClass*,而不是 optional&lt;MyClass&gt;*,这一事实暗示了这一点。 docs

该函数有四个参数。第一个是值的存储,在这种情况下,要么是空的,要么包含一个 magic_number 类的实例。第二个是在下一次出现的选项中找到的字符串列表。需要剩余两个参数来解决某些编译器上缺少部分模板特化和部分函数模板排序的问题。

这是我的看法:

Live On 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: --

奖金

the docs 获得提示,我认为你可以让它更优雅:

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”的静态断言错误(请参阅 release notes 了解 1.65)。

Live On Coliru

还是一样的输出。

【讨论】:

  • 我不敢相信我错过了这么明显的事情;感谢您的详细回答!
  • @RemyPrechelt 我认为这是鸭子打字的问题(在这种情况下为boost::any)。干杯!
猜你喜欢
  • 2021-06-06
  • 2021-03-16
  • 1970-01-01
  • 2019-01-14
  • 1970-01-01
  • 2020-10-05
  • 2018-10-18
  • 2014-04-09
  • 1970-01-01
相关资源
最近更新 更多