可变参数

C++ Variadic parameters

本文关键字:参数 变参      更新时间:2023-10-16

我正在设计一个方便的配置对象,它将从文件中加载配置值。为了确保有相同的默认值,程序员可以为每种类型的值声明它的类型和默认值。这样,就可以检查配置文件,并立即发现任何不正确的地方。例如,考虑以下配置文件httpd.conf:

port    8080
htdocs  ROOT
prelude true

和main中的配置对象:

int main() {
  Config conf("httpd.conf",
    "port", Config::INT, 80,
    "htdocs", Config::STRING, "default/",
    "preload", Config::BOOLEAN, false);

上面的代码将加载文件并验证port实际上是一个整数,它将加载htdocs,并发现文件中的"prelude"与配置中的任何注册值都不匹配,并发出一个错误:

第3行:未定义配置项"prelude"

我可以用一个旧的C可变参数来实现上述功能,但这些参数不是类型安全的。有什么方法可以用新的c++可变参数做到这一点吗?我所看到的例子都是千篇一律的。这里是值的三元组

我想设计一些易于在单个大调用中编写的东西,但这是类型安全的

如果不使用可变模板或函数并避免类型省略,您可能会:

#include <sstream>
#include <stdexcept>
class Configuration
{
    public:
    Configuration(const std::string& resource)
    // Initialize the resources: Program options, environment variables, files
    {}
    /// Get a raw configuration value for a key.
    /// Reurns true if the key exists
    bool get_raw(const std::string key, std::string& result) const {
        // Find the key in supplied resources and set the result string
        // trimming leading and trailing spaces
        return false;
    }
    template <typename T>
    T get(const std::string key) const;
    template <typename T>
    T get(const std::string key, const T& default_value) const;
};
template <typename T>
T Configuration::get(const std::string key) const {
    std::string str;
    if( ! get_raw(key, str)) throw std::runtime_error("Invalid Key");
    else {
        T result;
        std::istringstream is(str);
        is.unsetf(std::ios_base::basefield);
        is >> result;
        if( ! is.eof() || is.fail()) throw std::runtime_error("Invalid Value");
        return result;
    }
}
template <typename T>
T Configuration::get(const std::string key, const T& default_value) const {
    std::string str;
    // There might be a dilemma - is a non existing key an error?
    if( ! get_raw(key, str)) return default_value;
    else if(str.empty()) return default_value;
    else {
        T result;
        std::istringstream is(str);
        is.unsetf(std::ios_base::basefield);
        is >> result;
        if( ! is.eof() || is.fail()) throw std::runtime_error("Invalid Value");
        return result;
    }
}

// Usage
struct HttpConfiguration : public Configuration
{
    unsigned port;
    std::string htdocs;
    bool preload;
    HttpConfiguration()
    :   Configuration("httpd.conf"),
        port(get<unsigned>("port", 80)),
        htdocs(get<std::string>("htdocs", "default/")),
        preload(get<bool>("prelude", false)) // typo here
    {}
};

注意:类配置可以是任何管理配置源(看看Boost, POCO,…)。

如果你想使用可变模板,这只是一个开始:

template <class T>
struct Param {
  using Type = T;
  std::string name_;
  T default_value_;
};
template <class T>
auto MakeParam(std::string name, T default_value) -> Param<T> {
  return {name, default_value};
}
template <class T, class... Args>
auto ReadParams(Param<T> p, Args... args) -> void {
  ReadParams(p);
  ReadParams(args...);
}
template <class T>
auto ReadParams(Param<T> p) -> void {
  // here you can read from file
  cout << "param: '" << p.name_ << "' type: '"
       << typeid(typename Param<T>::Type).name() << "' defval: '"
       << p.default_value_ << "'" << endl;
}
int main() {
  ReadParams(MakeParam("param1", 0), MakeParam("param2", true),
             MakeParam("param3", "c-string"),
             MakeParam("param4", std::string{"c++str"}));
  return 0;
}