Templated get method to retrieve mixed data types from table

188 Views Asked by At

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)

2

There are 2 best solutions below

7
On BEST ANSWER

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) { }
3
On

In addition to the accepted answer I would like to add a demo with a little difference of verifying the correctness of the type without boilerplatting the code over all template specializations. As well correcting the explicit-template specialization in the class scope that is not allowed.

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;
}