从 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;
}
我知道标题没有意义,找不到更好的。
我需要为 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;
}