将 boost::program_options 与 std::optional 一起使用

问题描述

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 代表 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>> 的“解决方案”一般不太纯 因为

    • 它有可能用 ADL 可见的重载“感染”所有其他可能会干扰的代码。使用 operator>> 的代码比 boost 的 validate 函数
    • 它因此邀请 UB 由于 ODR 违规, 当另一个翻译单元可能合法地定义 另一个 operator>> 用于相同的参数。
  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,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

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,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"

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...