I know the title is not meaningful, couldn't find anything better.
I need to provide a C++ interface to an SQlite table, where I can store key/value/type configuration settings, such as
Key | Value | Type
PATH | /path/to/ | STRING
HAS_FEATURE | Y | BOOLEAN
REFRESH_RATE| 60 | INTEGER
For simplicity and flexibility purposes the datamodel hosts the values as strings but provides a column to retain the original data type.
This is how I have imagined a client to call such c++ interface.
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");
This is how I have imagined implementing it (I know the code won't compile as is, consider it as pseudo c++, I'm more questioning the design than the syntax here)
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
};
Is that a good implementation ? Any suggestions on how to improve it ?
I know I could have specialized the public get
directly per return type, but I would have duplicated some code in each template specialization (the type consistency check as well as the parameter retrieval)
Your approach will fail badly if you try to implement it out! Problem is:
return <parameter.type, RETURNTYPE>getImpl(parameter);
or with correct C++ syntax:
return getImpl<parameter.type, RETURNTYPE>(parameter);
Template parameters require to be compile time constants, which parameter.type
is not! So you would have to try something like this:
switch(parameter.type)
{
case STRING:
return getImpl<STRING, RETURNTYPE>(parameter);
//...
}
Does not look like you gained anything at all, does it?
You might try the other way round, though, specialising the getter itself:
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;
}
Or without templates (referring Nim's comment...):
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;
}
One change you might have noticed: I fixed constness for your parameters...
Side note: template specialisations as above are not allowed at class scope (above is written for shortness). In your true code, you have to move the specialisations out of the class:
struct S { template<typename T> void f(T t); };
template<> void S::f<int>(int t) { }