Minimalistic, self-contained class for reading a sectionless INI file When sections are ignored and no writing capability is needed, an INI reader becomes quite simple to implement. Here is my attempt at reading a sectionless INI file without any non-std dependencies.

The general idea is to store all key-value pairs in a &lt;string, string&gt; map, and use getters to convert the value string to desired type on demand.

#include &lt;fstream&gt;
#include &lt;iostream&gt;
#include &lt;map&gt;
#include &lt;string&gt;

class SectionlessINIReader {

private:
    std::map&lt;std::string, std::string&gt; dict;

public:

    // https://stackoverflow.com/a/1798170/3516684
    static std::string trim(const std::string&amp; str,
                 const std::string&amp; whitespace = " \t")
    {
        const auto strBegin = str.find_first_not_of(whitespace);
        if (strBegin == std::string::npos)
            return ""; // no content

        const auto strEnd = str.find_last_not_of(whitespace);
        const auto strRange = strEnd - strBegin + 1;

        return str.substr(strBegin, strRange);
    }

    SectionlessINIReader(const std::string&amp; filename) {

        std::ifstream file(filename);
        std::string line;
        int idxEq;
        while (std::getline(file, line))
        {

            if ((line.find_first_not_of(";#")==0) &amp;&amp;
                ((idxEq = line.find("=")) != std::string::npos)) {

                    std::string key = trim(line.substr(0,idxEq));
                    std::string value = trim(line.substr(idxEq+1));

                    dict[key] = value;
                }
        }
        file.close();
    }

    std::string get(std::string key) {
        return dict[key];
    }

    bool getLong(std::string key, long* p_value) {

        std::string value = dict[key];

        if (value=="") {
            return false;

        } else {
            *p_value = std::stol(dict[key]);
            return true;
        }
    }

    // bool getDouble(...)

};


The intended usage is:

SectionlessINIReader ini("config.ini");
long age;
if (ini.getLong("age", &amp;age))
    std::cout &lt;&lt; "Name: " &lt;&lt; ini.get("name") &lt;&lt; "; age: " &lt;&lt; age &lt;&lt; std::endl;
Include (only) what you use

This class uses nothing from &lt;iostream&gt;, so it's a waste of the compiler's time to include it.

Control the public interface

Is trim() really a facility that should be a part of SectionlessINIReader?  I don't think it belongs in the public interface, and more likely belongs in a private or protected section.

Make it easier to use

The getLong() member function has an awkward interface, requiring the user to pass an lvalue (worse, a pointer).  Consider returning a std::optional&lt;long&gt; instead, to convey the result in a single value.

Pass strings by const reference

There's no need to make a copy of the key argument to the accessors - that should be declared as const std::string&amp; key instead.

Accessors should be const

get() and getLong() both modify dict (they create a new empty mapping for any requested key that doesn't yet exist).  We don't want to do that, as it's wasteful.  We would have spotted this bug if we'd declared those members const (so that they could be used by code with only a const reference to the reader - that will be important if we later add writing functions).

We could write:

std::optional&lt;std::string&gt; get(std::string key) const
{
    auto const it = dict.find(key);
    if (it == dict.end())
        return {};

    return it-&gt;second;
}


And getLong() could use it:

std::optional&lt;long&gt; getLong(std::string key) const
{
    auto const value = get(key);
    if (!value.has_value())
        return {};

    return std::stol(value.value());
}


Open the code to extension

At present, the only way to add more types that can be read is to edit this class or to subclass it.  And it's not designed for subclassing (it has a non-virtual destructor), so it's closed to end-users.

One way to make it open for extension is to separate the string-to-value conversion from the string lookup, and allow users to specialize that part; our code could then use the appropriate specialization, or fail to compile if no conversion has been defined.

This would be the starting position:

namespace convert {

    // This function must be specialized for any extension types to
    // convert from string to value.
    template&lt;typename T&gt;
    std::optional&lt;T&gt; fromString(const std::string&amp;);
}


It's used in our get&lt;&gt;() function quite simply:

template&lt;typename T&gt;
std::optional&lt;T&gt; get(const std::string&amp; key) const
{
    auto const value = get(key);
    if (!value.has_value())
        return {};

    return convert::fromString&lt;T&gt;(value.value());
}


We could provide some common conversions:

template&lt;&gt;
std::optional&lt;std::string&gt; convert::fromString(const std::string&amp; s)
{ return s; }

template&lt;&gt;
std::optional&lt;long&gt; convert::fromString(const std::string&amp; s)
{ return std::stol(s); }

template&lt;&gt;
std::optional&lt;double&gt; convert::fromString(const std::string&amp; s)
{ return std::stod(s); }


Users can add their own conversions in similar manner.

Variation

Instead of creating a free function to specialize, we could make fromString() be a private static member function of SectionlessINIReader.  The choice may well depend on whether you might want to use fromString() from anywhere else.

Think about exceptions

std::stol() will throw an exception if the value can't be parsed as a long.  Do we want to pass this straight to our callers, or do we want to treat malformed values the same as missing values, and simply return an empty std::optional?  (There's no right or wrong answer here; I just want to be sure that we've made a conscious choice.)

Avoid narrowing conversions


    int idxEq;
    ...
        if (((idxEq = line.find("=")) != std::string::npos)) {



Here, we assign the result of find() (a size type) to idxEq (a signed integer) and then compare idxEq to std::string::npos (a size type again).  That's a potentially narrowing conversion which could change the sign, and a comparison between types of different signedness.  I know we don't expect keys to be long enough to overflow int, but it would be nice to silence the compiler warning here, and it's very easy to make it consistent:

    std::string::size_type idxEq;


Check the result of filesystem operations

In the constructor, we call file.close(), but never check the result.  If we want to be sure that we read the complete file, we need to ensure that close() has succeeded.

If we really don't care about whether we missed some data, then we could simply omit the close() and let the file's destructor do that for us - but I don't advise ignoring a failed close.

Inefficient use of search

There's no need to search all the way through a string just to see what its first character is:


if (line.find_first_not_of(";#") == 0)



It's clearer to examine the first character directly (after checking that the line has at least one character, of course).

Check for duplicate keys

When reading, we simply overwrite any key that appears more than once.  Consider emitting a warning, or throwing an exception.

Remove dead code

There's a remnant of some old code still hanging around, which should be deleted:


// bool getDouble(...)





Modified code

#include &lt;fstream&gt;
#include &lt;map&gt;
#include &lt;optional&gt;
#include &lt;stdexcept&gt;
#include &lt;string&gt;

class SectionlessINIReader
{

private:
    std::map&lt;std::string, std::string&gt; dict = {};

public:
    SectionlessINIReader(const std::string&amp; filename)
    {
        auto file = std::ifstream {filename};
        readStream(file);
    }

    SectionlessINIReader(std::istream&amp; in)
    {
        readStream(in);
    }

    std::optional&lt;std::string&gt; get(const std::string&amp; key) const
    {
        auto const it = dict.find(key);
        if (it == dict.end())
            return {};

        return it-&gt;second;
    }

    template&lt;typename T&gt;
    std::optional&lt;T&gt; get(const std::string&amp; key) const
    {
        auto const value = get(key);
        if (!value.has_value())
            return {};

        return fromString&lt;T&gt;(value.value());
    }

private:

    // https://stackoverflow.com/a/1798170/3516684
    static std::string trim(const std::string&amp; str,
                            const std::string&amp; whitespace = " \t")
    {
        const auto strBegin = str.find_first_not_of(whitespace);
        if (strBegin == std::string::npos)
            return {}; // no content

        const auto strEnd = str.find_last_not_of(whitespace);
        const auto strRange = strEnd - strBegin + 1;

        return str.substr(strBegin, strRange);
    }

    template&lt;typename T&gt;
    static std::optional&lt;T&gt; fromString(const std::string&amp;);

private:

    void readStream(std::istream&amp; in)
    {
        std::string line;
        while (std::getline(in, line)) {
            if (line.empty() or line[0] == ';' or line[0] == '#') {
                continue;
            }
            auto const idxEq = line.find('=');
            if (idxEq == line.npos) {
                // consider reporting an error here
                continue;
            }

            auto const key = trim(line.substr(0,idxEq));
            auto const value = trim(line.substr(idxEq+1));
            if (!dict.emplace(key, value).second) {
                throw std::runtime_error("Duplicate key in INI file");
            }
        }
        if (!in.eof()) {
            throw std::runtime_error("Failed to read INI file");
        }
    }
};

// String converters
template&lt;&gt;
std::optional&lt;std::string&gt; SectionlessINIReader::fromString(const std::string&amp; s)
{ return s; }

template&lt;&gt;
std::optional&lt;long&gt; SectionlessINIReader::fromString(const std::string&amp; s)
{ return std::stol(s); }

template&lt;&gt;
std::optional&lt;double&gt; SectionlessINIReader::fromString(const std::string&amp; s)
{ return std::stod(s); }

// we can add more specializations here, and user may also add
// specializations.




#include &lt;iostream&gt;
#include &lt;sstream&gt;
int main()
{
    std::istringstream contents("age = 30\n"
                                "favourite_drink = tea\n"
                                "name = Bob\n");

    const SectionlessINIReader ini(contents);
    auto age = ini.get&lt;long&gt;("age");
    if (age.has_value())
        std::cout &lt;&lt; "Name: " &lt;&lt; ini.get&lt;std::string&gt;("name").value_or("")
                  &lt;&lt; "; age: " &lt;&lt; age.value()
                  &lt;&lt; std::endl;
}