【问题标题】:How to handle spaces in Boost::program_options config files for custom option value types that are not strings?如何为非字符串的自定义选项值类型处理 Boost::program_options 配置文件中的空格?
【发布时间】:2020-03-14 12:40:09
【问题描述】:

这个问题涉及Boost::program_options 配置文件中值的解析。

我有一个简单的自定义数据结构:

struct Vector {
    double x, y, z;
};

我有一个格式为“(x, y, z)”的 istream 反序列化器,这是我从另一个 SO 帖子中借来的:

// https://codereview.stackexchange.com/a/93811/186081
struct Expect {
    char expected;
    Expect(char expected) : expected(expected) {}
    friend std::istream& operator>>(std::istream& is, Expect const& e) {
        char actual;
        if ((is >> actual) && (actual != e.expected)) {
            is.setstate(std::ios::failbit);
        }
        return is;
    }
};

template<typename CharT>
std::basic_istream<CharT> &
operator>>(std::basic_istream<CharT> &in, Vector &v) {
    in >> Expect('(') >> v.x
       >> Expect(',') >> v.y
       >> Expect(',') >> v.z
       >> Expect(')');
    return in;
}

我正在使用Vector 的实例作为Boost::program_options 的值存储:

Vector vector {0.0, 0.0, 0.0};
po::options_description opts("Usage");
opts.add_options()
      ("vector", po::value(&vector), "The vector");

po::variables_map vm;
po::store(po::parse_config_file("config.cfg", opts, true), vm);
po::notify(vm);

问题是如果向量表示包含空格,配置文件格式不起作用。例如,这个配置文件解析正确:

vector = (0.0,1.1,2.2)

但是,这个带有空格的,不解析:

vector = (0.0, 1.1, 2.2)

相反,program_options 抛出:

the argument ('(0.0, 1.1, 2.2)') for option 'vector' is invalid

但是,对于声明为 std::string 的选项,空格似乎没问题:

some_string = this is a string

我发现一些帖子提到使用引号,但这似乎不起作用(同样的错误):

vector = "(0.0, 1.1, 2.2)"

其他一些帖子建议使用自定义解析器,但我不确定如何实现这一点,而且处理几个空格似乎需要做很多工作。

我假设这种行为来自命令行选项的解析方式,即使这是配置文件解析。在这种情况下,像--vector (0.0, 1.1, 2.2) 这样的命令行没有多大意义(暂时忽略shell 保留字符() 的使用)

有什么好的方法可以处理吗?

【问题讨论】:

    标签: c++ config whitespace istream boost-program-options


    【解决方案1】:

    不,你不能……

    编辑:经过再三考虑,我认为您可以尝试修改分隔符,如https://en.cppreference.com/w/cpp/locale/ctype

    program_options 使用lexical_cast,这要求在操作员之后消费整个内容>>。当有空间时,内容永远不会被一个>>消耗,默认情况下,错误也是如此。

    因此您可以执行以下操作:

        struct Optional {
            char optional;
            Optional(char optional):optional(optional){}
            friend std::istream& operator>>(std::istream& is, Optional const& o) {
                char next;
                do{
                    next = is.peek();
                }while(next == o.optional && is.get());
                return is;
            }
        };
    
        struct vector_ctype : std::ctype<wchar_t> {
            bool do_is(mask m, char_type c) const {   
                if ((m & space) && c == L' ') {
                    return false; // space will NOT be classified as whitespace
                }
                return ctype::do_is(m, c); // leave the rest to the parent class
            } 
        };
    
    
        template<typename CharT>
        std::basic_istream<CharT> &
        operator>>(std::basic_istream<CharT> &in, Vector &v) {    
            std::locale default_locale = in.getloc();
            in.imbue(std::locale(default_locale, new vector_ctype()));
            in >> Expect('(') >> Optional(' ') >> v.x >> Optional(' ')
               >> Expect(',') >> Optional(' ') >> v.y >> Optional(' ')
               >> Expect(',') >> Optional(' ') >> v.z >> Optional(' ')
               >> Expect(')');
            in.imbue(default_locale);
            return in;
        }
    
    int main()
    {
        Vector v  = boost::lexical_cast<Vector>("(1,  2,  3)");
        std::cout << v.x <<"," <<v.y <<"," << v.z <<std::endl;
    }
    

    输出:

    1,2,3
    

    这应该会在 program_options 中为您提供正确的输出

    【讨论】:

    • 你知道为什么它适用于std::string 选项吗?如果vectorstd::string,则vector = (1, 2, 3) 将转换为"(1, 2, 3)",且空格保持不变。我正在考虑使用这个事实将选项视为字符串,然后再解析它。
    • @davidA 因为在内部它被重载了。复制std::string(和数组),并为泛型类型流式传输。但我认为您可以尝试修改 istream 的分隔符:en.cppreference.com/w/cpp/locale/ctype
    • 几年后重新审视:这似乎运作良好,但令我困惑的是,我似乎不需要带有语言环境 + 自定义方面的 im.imbue() - 只是使用Optional 结构似乎足以跳过空格。这是@nelson.l 的意图吗?
    【解决方案2】:

    您可以编写一个例程,从输入文件中过滤掉不需要的字符,然后将其传递给boost::program_options 进行解析,而不是编写自定义解析器。

    使用 SO answerRemove spaces from std::string in C++,这是一个基于您的代码的工作示例:

    #include <iostream>
    #include <sstream>
    #include <fstream>
    #include <string>
    #include <boost/program_options/options_description.hpp>
    #include <boost/program_options/parsers.hpp>
    #include <boost/program_options/variables_map.hpp>
    
    struct Vector {
      double x, y, z;
    };
    
    // https://codereview.stackexchange.com/a/93811/186081
    struct Expect {
        char expected;
        Expect(char expected) : expected(expected) {}
        friend std::istream& operator>>(std::istream& is, Expect const& e) {
            char actual;
            if ((is >> actual) && (actual != e.expected)) {
                is.setstate(std::ios::failbit);
            }
            return is;
        }
    };
    
    template<typename CharT>
    std::basic_istream<CharT> &
    operator>>(std::basic_istream<CharT> &in, Vector &v) {
        in >> Expect('(') >> v.x
           >> Expect(',') >> v.y
           >> Expect(',') >> v.z
           >> Expect(')');
        return in;
    }
    
    std::stringstream filter(const std::string& filename)
    {
      std::ifstream inputfile(filename);
    
      std::stringstream s;
      std::string line;
      while (std::getline(inputfile, line))
        {
          auto end_pos = std::remove(line.begin(), line.end(), ' ');
          line.erase(end_pos, line.end());
          s << line << '\n';
        }
      s.seekg(0, std::ios_base::beg);
    
      return s;
    }
    
    int main()
    {
      namespace po = boost::program_options;
    
      po::options_description opts("Usage");
      opts.add_options()
        ("vector", po::value<Vector>(), "The vector");
    
      auto input = filter("config.cfg");
      po::variables_map vm;
      po::store(po::parse_config_file(input, opts, true), vm);
      po::notify(vm);
    
      auto read_vec = vm["vector"].as<Vector>();
      std::cout << "vector is : {" << read_vec.x << ", " << read_vec.y << ", " << read_vec.z << "}" << std::endl;
    
      return 0;
    }
    

    要测试程序,您必须创建一个文件config.cfg,其中包含例如:

    vector = (1,  2, 3)
    

    (额外的间距是故意的,以测试例程)。

    这样,程序的输出是

    vector is : {1, 2, 3}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-01-16
      • 2020-08-27
      • 1970-01-01
      • 1970-01-01
      • 2023-03-11
      • 2011-02-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多