【问题标题】:Using boost::program_options with std::optional将 boost::program_options 与 std::optional 一起使用
【发布时间】:2021-06-06 22:07:15
【问题描述】:

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));
}

失败,目标类型不是istreamable:

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>, std::__1::optional<int>, boost::binary_op_detail::dont_care>::value'
"Target type is neither std::istream`able nor std::wistream`able"

【问题讨论】:

    标签: boost boost-program-options stdoptional


    【解决方案1】:

    validate(以及operator&gt;&gt;)之类的问题通常是ADL¹。

    您需要在关联的命名空间之一中声明重载。在这种情况下,因为int 是原始类型,所以唯一关联的命名空间来自库代码:

    • std for optionalvectorstringallocatorchar_traits(是的,这些都算!)
    • boostany

    您不希望将代码添加到这些命名空间中,因为当库实现细节发生变化时,您可能会干扰库函数或招致未来的破坏。

    如果你必须选择,你更愿意在这里选择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

    其他说明:

    1. 注入operator&gt;&gt; 的“解决方案”通常不太纯净 因为

      • 它有可能用可能干扰的 ADL 可见重载“感染”所有其他代码。使用operator&gt;&gt; 的代码比使用更多的代码 boost的validate函数
      • 它因此邀请UB,因为 ODR 违规, 当另一个翻译单位可能合法地定义 另一个 operator&gt;&gt; 用于相同的论点。
    2. 在最近的编译器上,你可以说 vm.contains 而不是略带辱骂的 vm.count

    3. 还有一个非流式类型的障碍,如果你定义一个默认值,你可能还需要用它指定 字符串表示

    上市

    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, 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
    
    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, typename Values>
        void validate(boost::any& v, Values const& values, 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
    

    Live Demo

    #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, 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, typename Values>
        void validate(boost::any& v, Values const& values, 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
    
    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, av, desc), 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"

    【讨论】:

    • Added 一个额外的“章节”展示了如何通过将 ADL 用于您自己的命名空间来减少让步,并提供更多的概括。 Live Demo
    • 多么棒的答案。感谢您回答我现在提出的问题我很快就会提出的问题(下周,我收集)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-01-30
    • 1970-01-01
    • 2011-09-14
    • 2020-08-03
    • 2017-12-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多