c++templatesboosttype-erasurelexical-cast

Templated get method to retrieve mixed data types from table


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)


Solution

  • 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) { }