从 table 中检索混合数据类型的模板化 get 方法

Templated get method to retrieve mixed data types from table

我知道标题没有意义,找不到更好的。

我需要为 SQlite table 提供一个 C++ 接口,我可以在其中存储 key/value/type 配置设置,例如

    Key     |   Value    |   Type
PATH        | /path/to/  |  STRING
HAS_FEATURE |    Y       |  BOOLEAN
REFRESH_RATE|    60      |  INTEGER

出于简单性和灵活性的目的,数据模型将值托管为字符串,但提供一列以保留原始数据类型。

这就是我想象的客户端调用这样的c++接口的方式。

Configuration c;
int refreshRate = c.get<int>("REFRESH_RATE");

// Next line throws since type won't match
std::string refreshRate = c.get<std::string>("REFRESH_RATE");

这就是我想象中的实现方式(我知道代码不会按原样编译,将其视为伪 C++,我对设计的质疑多于对语法的质疑)

class Parameter
{
    public:
        enum KnownTypes 
        {
            STRING = 0,
            BOOLEAN,
            INTEGER,
            DOUBLE,
            ...
        }

        std::string key;
        std::string value;
        KnownTypes type;
}

class Configuration 
{
    public:
        template<class RETURNTYPE>
        RETURNTYPE get(std::string& key)
        {
            // get parameter(eg. get cached value or from db...)
            const Parameter& parameter = retrieveFromDbOrCache(key);

            return <parameter.type, RETURNTYPE>getImpl(parameter);
        }

    private:
        template<int ENUMTYPE, class RETURNTYPE>
        RETURNTYPE getImpl(const Parameter& parameter)
        {
            throw "Tthe requested return type does not match with the actual parameter's type"; // shall never happen
        }

        template<Parameter::KnownTypes::STRING, std::string>
        std::string getImpl(const Parameter& parameter)
        {
            return parameter.value;
        }

        template<Parameter::KnownTypes::BOOLEAN, bool>
        std::string getImpl(const Parameter& parameter)
        {
            return parameter.value == "Y";
        }

        template<Parameter::KnownTypes::INTEGER, int>
        int getImpl(const Parameter& parameter)
        {
            return lexical_cast<int>(parameter.value)
        }

        // and so on, specialize once per known type
};

这是一个很好的实现吗?关于如何改进它有什么建议吗?

我知道我可以直接根据 return 类型专门化 public get,但我会在每个模板专门化中复制一些代码(类型一致性检查以及参数检索)

如果您尝试实施您的方法,您的方法将严重失败!问题是:

return <parameter.type, RETURNTYPE>getImpl(parameter);

或使用正确的 C++ 语法:

return getImpl<parameter.type, RETURNTYPE>(parameter);

模板参数要求是编译时常量,parameter.type不是!所以你必须尝试这样的事情:

switch(parameter.type)
{
case STRING:
    return getImpl<STRING, RETURNTYPE>(parameter);
//...
}

看起来你一点收获都没有,是吗?

不过,您可以尝试相反的方法,专门化 getter 本身:

public:
    template<class RETURNTYPE>
    RETURNTYPE get(std::string const& key);

    template<>
    std::string get<std::string>(std::string const& key)
    {
        return getImpl<STRING>(key);
    }
    template<>
    int get<int>(std::string const& key)
    {
        return lexical_cast<int>(getImpl<STRING>(key));
    }

private:
    template<KnownTypes Type>
    std::string getImpl(std::string const& key)
    {
        Parameter parameter = ...;
        if(parameter.type != Type)
            throw ...;
        return parameter.value;
    }

或者没有模板(参考 Nim 的评论...):

public:
    int getInt(std::string const& key)
    {
        return lexical_cast<int>(getImpl(STRING, key));
    }

private:
    inline std::string getImpl(KnownTypes type, std::string const& key)
    {
        Parameter parameter = ...;
        if(parameter.type != type)
            throw ...;
        return parameter.value;
    }

您可能已经注意到了一个变化:我修复了您的参数的常量性...

旁注:在 class 范围内不允许使用上述模板特化(以上内容是为了简短起见)。在您的真实代码中,您必须将专业化移出 class:

struct S { template<typename T> void f(T t); };

template<> void S::f<int>(int t) { }

除了已接受的答案之外,我还想添加一个演示,其中有一点不同,即验证类型的正确性,而无需对所有模板专业化的代码进行样板化。以及更正 class 范围内不允许的显式模板专业化。

class Parameter {
public:
  enum KnownTypes { STRING = 0, BOOLEAN, INTEGER, DOUBLE };

  std::string key;
  std::string value;
  KnownTypes type;
};

class Configuration {
public:
  template <class RETURNTYPE>
  RETURNTYPE get(std::string const& key) {
    // get parameter(eg. get cached value or from db...)
    std::map<std::string, Parameter> map{
      {"int", Parameter{"int", "100", Parameter::KnownTypes::INTEGER}},
      {"string", Parameter{"string", "string_value", Parameter::KnownTypes::STRING}},
      {"throwMe", Parameter{"throwMe", "throw", Parameter::KnownTypes::DOUBLE}},
      {"bool", Parameter{"bool", "Y", Parameter::KnownTypes::BOOLEAN}}};
    const Parameter& parameter = map.at(key);

    bool isMatchingType = false;
    switch (parameter.type) {
    case Parameter::STRING:
      isMatchingType = std::is_same<RETURNTYPE, std::string>::value;
      break;
    case Parameter::BOOLEAN:
      isMatchingType = std::is_same<RETURNTYPE, bool>::value;
      break;
    case Parameter::INTEGER:
      isMatchingType = std::is_same<RETURNTYPE, int>::value;
      break;
    case Parameter::DOUBLE:
      isMatchingType = std::is_same<RETURNTYPE, double>::value;
      break;
    };

    if (!isMatchingType)
      throw "Tthe requested return type does not match with the actual parameter's type";

    return getImpl<RETURNTYPE>(parameter);
  }

private:
  template <class RETURNTYPE>
  RETURNTYPE getImpl(const Parameter& parameter);
};

template <>
std::string Configuration::getImpl<std::string>(const Parameter& parameter) {
  return parameter.value;
}

template <>
bool Configuration::getImpl<bool>(const Parameter& parameter) {
  return parameter.value == "Y";
}

template <>
int Configuration::getImpl<int>(const Parameter& parameter) {
  return std::stoi(parameter.value);
}

int main() {
  Configuration conf;
  cerr << conf.get<int>("int") << endl;
  cerr << conf.get<bool>("bool") << endl;
  cerr << conf.get<string>("string") << endl;
  cerr << conf.get<string>("throwMe") << endl;

  return 0;
}