-1

I'm currently using the class given in this tutorial: http://www.dreamincode.net/forums/topic/183191-create-a-simple-configuration-file-parser/

Initially it worked fine, but since I split the single source file into seperate header and cpp files I've been unable to call the getValueOfKey function

header:

#ifndef CONFIGFILE_H
#define CONFIGFILE_H

#include <iostream>
#include <string>
#include <sstream>
#include <map>
#include <fstream>
#include <typeinfo>

class ConfigFile
{
private:
        std::map<std::string, std::string> contents;
    std::string fName;
    void removeComment(std::string &line) const;
    bool onlyWhitespace(const std::string &line) const;
    bool validLine(const std::string &line) const;
    void extractKey(std::string &key, size_t const &sepPos, const std::string &line) const;
    void extractValue(std::string &value, size_t const &sepPos, const std::string &line) const;
    void extractContents(const std::string &line);
    void parseLine(const std::string &line, size_t const lineNo);
    void ExtractKeys(); 
public:
    ConfigFile(const std::string &fName);
    bool keyExists(const std::string &key) const;
    template <typename ValueType>
    ValueType getValueOfKey(const std::string &key, ValueType const &defaultValue) const;
};


#endif  /* CONFIGFILE_H */

cpp:

#include "ConfigFile.h"

std::map<std::string, std::string> contents;
std::string fName;

template <typename T>
static std::string T_to_string(T const &val)
{
    std::ostringstream ostr;
    ostr << val;

    return ostr.str();
}

template <typename T>
static T string_to_T(std::string const &val)
{
    std::istringstream istr(val);
    T returnVal;
    if (!(istr >> returnVal))
        std::cout << "CFG: Not a valid " << (std::string)typeid (T).name() << " received!\n" << std::endl;

    return returnVal;
}

template <>
std::string string_to_T(std::string const &val)
{
    return val;
}

void ConfigFile::removeComment(std::string &line) const
{
    if (line.find(';') != line.npos)
        line.erase(line.find(';'));
}

bool ConfigFile::onlyWhitespace(const std::string &line) const
{
    return (line.find_first_not_of(' ') == line.npos);
}

bool ConfigFile::validLine(const std::string &line) const
{
    std::string temp = line;
    temp.erase(0, temp.find_first_not_of("\t "));
    if (temp[0] == '=')
        return false;

    for (size_t i = temp.find('=') + 1; i < temp.length(); i++)
        if (temp[i] != ' ')
            return true;

    return false;
}

void ConfigFile::extractKey(std::string &key, size_t const &sepPos, const std::string &line) const
{
    key = line.substr(0, sepPos);
    if (key.find('\t') != line.npos || key.find(' ') != line.npos)
        key.erase(key.find_first_of("\t "));
}

void ConfigFile::extractValue(std::string &value, size_t const &sepPos, const std::string &line) const
{
    value = line.substr(sepPos + 1);
    value.erase(0, value.find_first_not_of("\t "));
    value.erase(value.find_last_not_of("\t ") + 1);
}

void ConfigFile::extractContents(const std::string &line)
{
    std::string temp = line;
    temp.erase(0, temp.find_first_not_of("\t "));
    size_t sepPos = temp.find('=');

    std::string key, value;
    extractKey(key, sepPos, temp);
    extractValue(value, sepPos, temp);

    if (!keyExists(key))
        contents.insert(std::pair<std::string, std::string > (key, value));
    else
        std::cout << "CFG: Can only have unique key names!\n" << std::endl;
}

void ConfigFile::parseLine(const std::string &line, size_t const lineNo)
{
    if (line.find('=') == line.npos)
        std::cout << "CFG: Couldn't find separator on line: " << T_to_string(lineNo) << "\n" << std::endl;

    if (!validLine(line))
        std::cout << "CFG: Bad format for line: " << T_to_string(lineNo) << "\n" << std::endl;

    extractContents(line);
}

void ConfigFile::ExtractKeys()
{
    std::ifstream file;
    file.open(fName.c_str());
    if (!file)
        std::cout << "CFG: File " << fName << " couldn't be found!\n" << std::endl;

    std::string line;
    size_t lineNo = 0;
    while (std::getline(file, line))
    {
        lineNo++;
        std::string temp = line;

        if (temp.empty())
            continue;

        removeComment(temp);
        if (onlyWhitespace(temp))
            continue;

        parseLine(temp, lineNo);
    }

    file.close();
}

ConfigFile::ConfigFile(const std::string &fName)
{
    this->fName = fName;
    ExtractKeys();
}

bool ConfigFile::keyExists(const std::string &key) const
{
    return contents.find(key) != contents.end();
}

template <typename ValueType>
ValueType ConfigFile::getValueOfKey(const std::string &key, ValueType const &defaultValue = ValueType()) const
{
    if (!keyExists(key))
        return defaultValue;

    return string_to_T<ValueType> (contents.find(key)->second);
}

I am attempting to call it using the same method as when it was a single file, something like std::cout << Config.getValueOfKey<std::string>("test");, but now I am getting the following compiler error

main.cpp: In function 'int main(int, char**)':
main.cpp:29:71: error: no matching function for call to 'ConfigFile::getValueOfKey(const char [5])'
main.cpp:29:71: note: candidate is:
In file included from main.h:17:0,
                 from main.cpp:9:
ConfigFile.h:35:12: note: template<class ValueType> ValueType ConfigFile::getValueOfKey(const string&, const ValueType&) const
ConfigFile.h:35:12: note:   template argument deduction/substitution failed:
main.cpp:29:71: note:   candidate expects 2 arguments, 1 provided

Given my poor grasp on templates I can't really see what this error is trying to tell me, I have tried passing a direct string instead of a char array to no avail. Any help or explanation would be greatly appreciated, my heads worn a nice hole in the desk over the past few hours.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Matthew Watson
  • 223
  • 1
  • 3
  • 7

1 Answers1

2

You declared the method taking 2 arguments:

ValueType getValueOfKey(const std::string &key, ValueType const &defaultValue) const;
//                                          |                           |
//                                   first parameter             second parameter

and only supply one:

Config.getValueOfKey<std::string>("test");

I've yet to encounter a compiler that guesses what you mean without any help.

You'll need to move the default to the header file, where you declare the method:

ValueType getValueOfKey(const std::string &key, ValueType const &defaultValue = ValueType()) const;
    //                                          |                           |
    //                                   first parameter             second parameter

You'll probably get a linker error afterwards, so you might want to check this.

Community
  • 1
  • 1
Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • How about a community-driven `so++`? It turns SO questions into executable code, and features crystal-ball code deduction. – Kerrek SB Nov 05 '12 at 16:13
  • I've yet to encounter a C++ Standard that allows for guessing what you mean without any help. – Lightness Races in Orbit Nov 05 '12 at 16:16
  • @KerrekSB: Well, we're half-way there... – Lightness Races in Orbit Nov 05 '12 at 16:17
  • Ah, I was under the impression the default had to go in the definition, thanks – Matthew Watson Nov 05 '12 at 16:33
  • @LightnessRacesinOrbit - *I've yet to encounter a C++ Standard that allows for guessing what you mean without any help.* That is exactly what any decent compiler will do. A good compiler will try to make sense of an ill-formed input and then carry on so as to detect and report multiple errors. It's quite painful when dealing with a toolchain that stops dead at the very first error in the input. – David Hammen Nov 05 '12 at 18:04
  • @DavidHammen: I meant in terms of producing code, not of producing optimally-useful diagnostics. I believe Luchian meant the same! And, even if I were talking about diagnostics, the method of generating them is not specified by the C++ standard. – Lightness Races in Orbit Nov 05 '12 at 18:06
  • @LightnessRacesinOrbit - The standard is pretty lax in this regard. "If a program contains a violation of any diagnosable rule, a conforming implementation shall issue at least one diagnostic message ..." An implementation that auto-corrects my `class Foo { ... } /* missing semicolon */ class Bar ...` problem is compliant so long as it does somehow complain about the problem. – David Hammen Nov 05 '12 at 18:34
  • @DavidHammen: I basically just said that :) – Lightness Races in Orbit Nov 05 '12 at 20:32