Calling overloaded constructor from constructor initialisation list

3.1k views Asked by At

In the code below, my intent is to call one of two overloaded constructors for the kap (class opacity) based on what arguments are passed to the object of class material:

class opacity{
 private:
  int mode;
  double kap_const;
  double kappa_array[10][10];

 public:
  opacity(double constkap);  // picking the constructor sets the mode
  opacity(char* Datafile);
  double value(double T, double P); // will return a constant or interpolate
};

opacity::opacity(double constkap):mode(1){
  kap_const = constkap;
}

opacity::opacity(char* Datafile):mode(2){
  // read file into kappa_array...
}

class Matter {
 public:
  Matter(int i, double k, char* filename); // many more values are actually passed
  opacity kap;
  int x;  // dummy thing
  // more variables, call some functions
};

Matter::Matter(int i, double k, char * filename)
 :x(k>0? this->kap(x): this->kap(filename) ) {
  // ... rest of initialisation
 }

This is however not working:

test.cpp: In constructor 'Matter::Matter(int, double, char*)':
test.cpp:32:21: error: no match for call to '(opacity) (void*&)'
test.cpp:32:42: error: no match for call to '(opacity) (char*&)'
test.cpp:32:44: error: no matching function for call to 'opacity::opacity()'
test.cpp:32:44: note: candidates are:
test.cpp:20:1: note: opacity::opacity(char*)
test.cpp:20:1: note:   candidate expects 1 argument, 0 provided
test.cpp:16:1: note: opacity::opacity(double)
test.cpp:16:1: note:   candidate expects 1 argument, 0 provided
test.cpp:4:7: note: opacity::opacity(const opacity&)
test.cpp:4:7: note:   candidate expects 1 argument, 0 provided

The first thing I had tried,

Matter::Matter(int i, double k, char * filename)
 :kap(k>0? k: filename) {   // use k<0 as a flag to read from filename
  // ... rest of initialisation
}

also failed, because "the result of a ternary operator always has to be the same type" for compile-time reasons, as pointed out in a similar question (although they were not explained there, it seems).

Now, the inelegant solution would be to also overload the Matter constructor based on the arguments that the kap constructor should receive, but this is (1) very inelegant, especially since the Matter constructor takes many variables and performs many actions (so a lot of code would be duplicated just to vary the kap part of the constructor initialisation list), and (2) this can get out of hand if there is another class used with Matter that also has different constructors: for M classes with N c'tors, one ends up with N^ M combinations...

Would someone have a suggestion or a work-around? Thanks in advance!

4

There are 4 answers

5
Andy Thomas On BEST ANSWER

If opacity has a copy constructor, you could accomplish this in the initialization list, avoiding a default constructor, but at the cost of a copy:

  Matter::Matter(int i, double k, char * filename)
     :kap( ( 0 < k ) ? opacity(k) : opacity( filename ) ) { ... }
1
Johannes Schaub - litb On

You will have to live with adding a default constructor to opacity (which perhaps sets mode to 0 to indicate an invalid mode) and assign to kap in the constructor body.

Matter::Matter(int i, double k, char * filename) {
  if(k > 0)
    kap = opacity(k);
  else
    kap = opacity(filename);
}

The parameter k is a runtime value. It is not possible to make types and overloading result depend on runtime values.

0
joshperry On

To obviate copy overhead, and assuming you have a C++0x compiler, you could give opacity a move constructor and have a static function provide an instance of opacity based on your logic and initialize your kap member with the returned temporary opacity.

You'd probably want to make kappa_array some pointer like auto_ptr<double>. Though if this data is used in a tight-loop, the savings from moveability may be dubious compared to the cost of locality and dereferencing the pointer.

opacity& get_opacity(double k, char * filename) {
    if(k > 0)
        return opacity(k);
    else
        return opacity(filename);
}

Matter::Mater(int i, double k, char * filename)
    : kap(get_opacity(k, filename) {
   //...
}

opacity::opacity(opacity&& other)
    : mode(other.mode),
      kap_const(other.kap_const),
      kappa_array(std::move(kappa_array)) { }

Please don't test me on this, I'm pretty new with move semantics and rvalue references myself...

2
Mark Ransom On

You can't use a ternary operator to select function overrides because the result of the operator only has a single type. In the case where the different branches have different types, they will be coerced to the result type or there will be a compile time error. See http://en.wikipedia.org/wiki/%3F:#Result_type

It couldn't really be any other way. Types need to be known by the compiler at compile time, but the result of the operation isn't known until run time.