Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a parameter to get others

I have a struct/class that has many attributes among which one is principal. when constructing an instance of this class, the principal attribute must be given in parameter, but any of the others may or may not be provided. When an attribute is not provided as a parameter it should be computed from the principal attribute.

I have no clue on how to write such a class without writing an exponential number of constructors; or using setters after each constructor that would have only one parameter corresponding to the principal attribute.

I give here a minimal example of what would be ideal for me. But this of course doesn't compile:

#include <cstdlib>
#include <iostream>

using namespace std;

struct StringInfo
{
  string str;
  int importance;

  StringInfo(string strP, int importanceP = strP.length()): 
  str(strP), importance(importanceP){}

};    

int main(int argc, char** argv) {
  string test = "test";
  StringInfo info(test);  
  cout << info.importance << endl;
}

Do you have any better (non-exponential) solution? Thanks in advance.

like image 751
Issam T. Avatar asked Oct 21 '22 05:10

Issam T.


1 Answers

You practically want a behaviour similar to Python's optional parameters, eg :

callFunction(param1=2, param3="hello")

You can do this in C++ if you have as a member a map of type erased key-value pairs :

map<string, ErasedType> _m; 

The key of the map is a string with the description of the member (it's up to you to choose this).

Let's leave for now ErasedType. Provided that you have such a member you could write :

class MyClass
{
    map<string, ErasedType> _m;
public:
    MyClass(map<string, ErasedType> const& args) : _m(args) {}
};

So that you would only specify the keys you want to and assign specific values for them. An example of constructing the input for such a constructor is

map<string, ErasedType> args = { {"arg1", 1}, {"arg1", 1.2} };

Then accessing the particular value would take a member function like so :

    ErasedType get(string const& name) { return _m[name]; }

Now on the ErasedType . The most typical approach would be to have it as a union of all possible types you keep :

union ErasedType { // this would be a variant type
    int n;
    double d;
    char c;
};

A second idea would be to have the map values be each one an instance of the boost::any container. A third idea (more old school) would be to use void*

Toy Implementation

#include <map>
#include <string>
#include <iostream>
#include <boost/any.hpp>

using namespace std;
using boost::any_cast;

class OptionalMembers
{
    map<string, boost::any> _m;
public:
    OptionalMembers(map<string, boost::any> const& args) : _m(args) 
    {
        // Here you would initialize only those members 
        // not specified in args with your default values
        if (args.end() == args.find("argName")) {  
            // initialize it yourself
        }
        // same for the other members
    }
    template<typename T>
    T get(string const& memberName) {
        return any_cast<T>(_m[memberName]);
    }
};

int main() 
{
    // say I want my OptionalMembers class to contain
    // int    argInt
    // string argString
    // and then more members that won't be given 
    // as arguments to the constuctor

    std::string text("Hello!");
    OptionalMembers object({ { "argInt", 1 }, { "argString", text } });

    cout << object.get<int>("argInt") << endl;
    cout << object.get<string>("argString") << endl;

    return 0;
}

In the previous example you are free to have as many "members" as you want and specify each one at will in the constructor

like image 75
Nikos Athanasiou Avatar answered Oct 27 '22 09:10

Nikos Athanasiou