Question

As an exercise, I recently implemented a simple settings reader class which reads setting values from an INI file into an std::map<std::string, std::string>. The reader method looks like the following:

std::map<std::string, std::string> readSettings() {
    std::map<std::string, std::string> settings;

    for(std::string rawSetting; std::getline(settingsFile, rawSetting); )
        settings.insert(parseRawSettingIntoPair(rawSetting));

    return settings;
}

I feel a bit redundant specifying the type of renturn value twice:

  • firstly in the method declaration,
  • secondly when declaring the local variable, settings.

I know that in the compiler can deduce the return type if I use the auto specifier in the declaration of readSettings(), but this feature will force me to keep its definition in the header file too. Is there any better approach to reduce such repetitions?

Was it helpful?

Solution

Some options:

  • An alias like using Settings = std::map<std::string, std::string> cuts down on typing that all out, but is still repeated
Settings readSettings() {
    Settings settings;

    for(std::string rawSetting; std::getline(settingsFile, rawSetting); )
        settings.insert(parseRawSettingIntoPair(rawSetting));

    return settings;
}
  • Direct construct the return value from a coroutine - still quite wordy
std::map<std::string, std::string> readSettings() {
    using coro_t = boost::asymmetric_coroutine<std::pair<std::string, std::string>>;
    coro_t::pull_type source([&](coro_t::push_type& sink) { 
        for(std::string rawSetting; std::getline(settingsFile, rawSetting); )
            sink(parseRawSettingIntoPair(rawSetting));
    });
    return { begin(source), end(source) };
}
  • Both of those ( because why not :P )

OTHER TIPS

You can just utilize typedefs.

typedef std::map<std::string, std::string> Settings;

Settings readSettings() {
    Settings settings;
    for(std::string rawSetting; std::getline(settingsFile, rawSetting); )
        settings.insert(parseRawSettingIntoPair(rawSetting));
    return settings;
}

... not exactly groundbreaking material, but does the job. As for redundancy, it's all syntax as I see it -- not like anything which is prone to bugs at runtime.

Trying to make code as easy to read and write as possible should not precede making sure it's reliable and well-tested so that you don't have to bother with constantly changing it and re-reading it if you ask me, but the simple typedef example above is a quick and simple solution to ease the effort without spending much time thinking about it or requiring the compiler to deduce the return type from the implementation with auto (which would require the implementation to be visible to callers as you pointed out).

Finally it makes it easy to change Settings to a different type that has the same interface requirements if you ever need it -- only requires changing one line of code.

Just for completeness, here is an approach that does not return the result as an STL collection. Instead, it passes data into a callback. This may or may not satisfy your project's requirement; it is mentioned here just for completeness.

Two caveats.

Firstly, the signature for the std::function is uglier than the std::map, with nested angled brackets (template), parentheses (the arguments to the function), and a void for no benefit.

Secondly, if the person who provides the callback does something stupid inside the callback (such as trying to modify the collection or manipulate the same class), undefined behavior can be triggered. In other words, this callback approach is fragile unless all users of the code are well-informed.

Once again, this is provided for completeness' sake, not as a recommended practice.

void readSettings(std::function<
    void(const std::string& key, const std::string& value)> func)
{
    for(std::string rawSetting; std::getline(settingsFile, rawSetting); )
    {
        std::string key;
        std::string value;
        std::tie(key, value) = parseRawSettingIntoPair(rawSetting);
        func(key, value);
    }
}

The main motivation for writing this answer is if the caller (consumer of the data) is already known for refusing to use the same STL collection type as the function. In this case, the caller's insistence in using a different type means glue code is needed, so the function's author might as well turn itself into the glue code.

Such situation happens when the two side of code are maintained by different camps of developers.

Licensed under: CC-BY-SA with attribution
scroll top