2

I'm building a class which I want to configure using various parameters which may one of: int, double and string (or const char * for that matter). In a language like Ruby, I would build an initialization function that takes a hash keyed by a string. For example:

class Example
  def init_with_params params
    puts params
  end
end

e = Example.new
e.init_with_params({ "OutputFile" => "/tmp/out.txt", "CaptureFPS" => 25.0, "RetryDelaySeconds" => 5 })

How can I create a similar behavior in C++?

Looking around I found several posts talking about boost::variant. I would rather avoid using boost if by limiting the different types to the 3 types I mentioned above a rather clean solution can be made.

Edit: I agree that using a well designed and widely tested code such as Boost.Variant is much better than re-implementing the same idea. In this case, the problem is reduced to using only 3 basic types, so I was looking for the simplest possible way of implementing it.

miluz
  • 1,353
  • 3
  • 14
  • 22
  • What are you trying to do? Can you just have a class with a `int` `double` and `string` and then just make a normal constructor? – Fantastic Mr Fox Sep 23 '14 at 06:31
  • 2
    You can store all values as strings then have `get()` methods to return the strings converted to the relevant type. – Galik Sep 23 '14 at 06:32
  • I want to allow a flexible configuration which I can read for example from a .yaml file. Values are of different types {int, double, string} so conventional map won't work. – miluz Sep 23 '14 at 06:35
  • 1
    For a fixed number of trivial types (like `int` and `double`, but **not** `std::string`), you can knock together a simple discriminated union, using a union for the value and an enumeration to give the type. If it's more complicated (an unbounded set of types, or non-trivial types), then just use Boost.Variant. It's header-only, and quite fiddly to reinvent. – Mike Seymour Sep 23 '14 at 06:38
  • You can use `char*` to get you a pointer to a string into the union. Of course in that case you need to do the memory management by yourself. – VoidStar Sep 23 '14 at 06:43
  • Teh simplest possible way is *still* boost. Take the header, put in your project, use. Simple as that, and a 'standard' way of achieving what you want without having to bother writing your own. – gbjbaanb Sep 23 '14 at 07:28
  • Due to project constraints using Boost would have to be really justified (they had lots of compilation issues with Andoird NDK as I am told), and therefore this path is a high-effort one. Correct me if I'm wrong but boost/variant/variant.hpp doesn't seem like a small isolated header - it contains quite a few #include statements to various parts of boost. – miluz Sep 23 '14 at 07:37

4 Answers4

6

If you want to avoid the use of Boost, you can copy-paste the code of Boost.Variant into your project. It's header-only anyway. Or you can reinvent the wheel by implementing what's commonly called a "discriminated union."

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
5

Here is a cut down version of something I regularly use to store program configuration properties, maybe you will find it useful:

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

class config
{
    // everything is stored internally as std::strings
    // usually read from a configuration file
    typedef std::map<std::string, std::string> prop_map;
    typedef prop_map::const_iterator prop_map_citer;

    prop_map props;

public:
    // example constructor. Normally I have a method to read the
    // values in from a file
    config(std::initializer_list<std::pair<std::string, std::string>> list)
    {
        for(const auto& p: list)
            props[p.first] = p.second;
    }

    // values are converted as they are requested to whatever types
    // they need to be. Also a default may be given in case the value
    // was missing from the configuration file
    template<typename T>
    T get(const std::string& s, const T& dflt = T()) const
    {
        prop_map_citer found = props.find(s);

        if(found == props.end())
            return dflt;

        T t;
        std::istringstream(found->second) >> std::boolalpha >> t;
        return t;
    }

    // std::strings need special handling (no conversion)
    std::string get(const std::string& s, const std::string& dflt = "") const
    {
        prop_map_citer found = props.find(s);
        return found != props.end() ? found->second : dflt;
    }

};

int main()
{
    const config cfg =
    {
        {"OutputFile", "/tmp/out.txt"}
        , {"CaptureFPS", "25.0"}
        , {"RetryDelaySeconds", "5"}
    };

    std::string s;
    float f;
    int i;

    s = cfg.get("OutputFile");
    f = cfg.get<float>("CaptureFPS");
    i = cfg.get<int>("RetryDelaySeconds");

    std::cout << "s: " << s << '\n';
    std::cout << "f: " << f << '\n';
    std::cout << "i: " << i << '\n';
}
Galik
  • 47,303
  • 4
  • 80
  • 117
3

The sensible thing to do would be to use Boost.Variant, which is developed by expert C++ programmers and honed and tested by hundreds of projects.

But if that is not an option for you, I see the following alternatives:

  1. Reimplement Boost.Variant yourself. It will be a wonderful learning excercise, but it will take a lot of time to get right (and then more lots of time to fix all the bugs).

  2. Resign on time efficiency, store all the types in a std::string, and convert them in getters (originally suggested in a comment by @Galik).

  3. Resign on memory efficiency and store all three types in your class.

  4. If you have aversion against Boost specifically, use a different library which provides a variant type (like Qt's QVariant).

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
2

If I have understood well, you want a std::map<A, B> (or, even better, a std::unordered_map<> since you want hashes) where A is a string and B can be a int, double or std::string.

For B you can use boost::any<>. Otherwise, if you don't want to use boost, you can use a discriminated union.

Claudio
  • 10,614
  • 4
  • 31
  • 71